diff --git a/cli.js b/cli.js index 1e7ef10..ba29988 100755 --- a/cli.js +++ b/cli.js @@ -41,7 +41,8 @@ const cli = meow(` -d --out-dir Output directory (default dist) -s --static Output static HTML without JS bundle -t --template Path to custom HTML template - + --basename Basename for URL paths + --title Page title `, { flags: { // dev diff --git a/docs/JSX.md b/docs/JSX.md new file mode 100644 index 0000000..b1f5e63 --- /dev/null +++ b/docs/JSX.md @@ -0,0 +1,19 @@ +--- +--- + +# Using JSX + +x0 supports Compositor JSX format, which allows writing files as pure JSX without any JavaScript syntax. + +```jsx +--- +title: JSX Example +--- +import { Box, Heading } from 'rebass' + + + + Hello + + +``` diff --git a/docs/MDX.md b/docs/MDX.md new file mode 100644 index 0000000..aa4b7f6 --- /dev/null +++ b/docs/MDX.md @@ -0,0 +1,20 @@ + +# Using MDX + +x0 also supports [MDX][mdx] format out of the box. +MDX allows you to mix markdown syntax with JSX to render React components. + +```mdx +--- +title: MDX Example +--- +import { Box } from 'rebass' + +# Hello + + + Beep + +``` + +[mdx]: https://github.com/mdx-js/mdx diff --git a/docs/_app.js b/docs/_app.js index 478845d..839a66d 100644 --- a/docs/_app.js +++ b/docs/_app.js @@ -8,9 +8,47 @@ import { Box, Container, } from 'rebass' +import sortBy from 'lodash.sortby' import LandingLayout from './_layout' import theme from './_theme' +import X0 from './_logo' + +const navOrder = [ + 'index', + 'getting-started', + 'markdown', + 'react', + 'MDX', + 'JSX', + 'routing', + 'custom-app', + 'components', + 'ScopeProvider', + 'SidebarLayout', + 'LivePreview', + 'LiveEditor', + 'cli-options', + 'exporting', + 'examples', +] +const pageNames = { + index: 'Home', + 'cli-options': 'CLI Options' +} + +const sortRoutes = routes => [ + ...sortBy([...routes], a => { + const i = navOrder.indexOf(a.name) + return i < 0 ? Infinity : i + }) +].map(route => { + if (!pageNames[route.name]) return route + return { + ...route, + name: pageNames[route.name] + } +}) export default class App extends React.Component { static defaultProps = { @@ -29,10 +67,16 @@ export default class App extends React.Component { ? LandingLayout : SidebarLayout + const nav = sortRoutes(routes) + return ( - + } + /> ) diff --git a/docs/_logo-rect.js b/docs/_logo-rect.js new file mode 100644 index 0000000..8adbfd9 --- /dev/null +++ b/docs/_logo-rect.js @@ -0,0 +1,27 @@ +import React from 'react' +import X0 from './_logo' + +export default ({ + size = 1024 +}) => + + + + + + diff --git a/docs/_logo.js b/docs/_logo.js new file mode 100644 index 0000000..cde8154 --- /dev/null +++ b/docs/_logo.js @@ -0,0 +1,29 @@ +import React from 'react' + +export default ({ + size = 128, + color = 'currentcolor' +}) => + + + + + + + + diff --git a/docs/cli-options.md b/docs/cli-options.md new file mode 100644 index 0000000..ccfa293 --- /dev/null +++ b/docs/cli-options.md @@ -0,0 +1,39 @@ +--- +name: CLI Options +--- + +# CLI Options + +``` +--webpack Path to webpack config file +--match String to match routes against using minimatch +``` + +The following options are used for the development server. + +``` +-o --open Open dev server in default browser +-p --port Port for dev server +--analyze Runs with webpack-bundle-analyzer plugin +``` + +The following options are used for static export. + +``` +-d --out-dir Output directory (default dist) +-s --static Output static HTML without JS bundle +-t --template Path to custom HTML template +--basename Basename for URL paths +--title Page title +``` + +## package.json + +CLI options can also be specified in a `package.json` field named `x0`. + +```json +"x0": { + "title": "Hello", + "basename": "/my-site" +} +``` diff --git a/docs/components/LiveEditor.md b/docs/components/LiveEditor.md new file mode 100644 index 0000000..cd89867 --- /dev/null +++ b/docs/components/LiveEditor.md @@ -0,0 +1,17 @@ + +# LiveEditor + +The LiveEditor component can be used in React components outside of markdown code fences. +When used within a [ScopeProvider](ScopeProvider) component, there's no need to pass a custom `scope` object. + +```jsx +import React from 'react' +import { LiveEditor } from '@compositor/x0/components' + +const code = `` + +export default props => + +``` diff --git a/docs/components/LivePreview.md b/docs/components/LivePreview.md new file mode 100644 index 0000000..3d5b87d --- /dev/null +++ b/docs/components/LivePreview.md @@ -0,0 +1,17 @@ + +# LivePreview + +The LivePreview component can be used in React components outside of markdown code fences. +When used within a [ScopeProvider](ScopeProvider) component, there's no need to pass a custom `scope` object. + +```jsx +import React from 'react' +import { LivePreview } from '@compositor/x0/components' + +const code = `` + +export default props => + +``` diff --git a/docs/components/ScopeProvider.md b/docs/components/ScopeProvider.md new file mode 100644 index 0000000..09712bb --- /dev/null +++ b/docs/components/ScopeProvider.md @@ -0,0 +1,17 @@ + +# ScopeProvider + +The ScopeProvider component allows you to customize the components that are used to render markdown elements +and to provide components in scope for rendering code fences as live previews. +It's best to use this component in a [Custom App](/custom-app) component. + +```jsx +import React from 'react' +import ScopeProvider from '@compositor/x0/components' +import * as scope from '../src' + +export default props => + + {props.children} + +``` diff --git a/docs/components/SidebarLayout.md b/docs/components/SidebarLayout.md new file mode 100644 index 0000000..7ffcc1d --- /dev/null +++ b/docs/components/SidebarLayout.md @@ -0,0 +1,134 @@ + +# SidebarLayout + +The SidebarLayout component can be used to quickly create a documentation site with navigation. +This site uses the SidebarLayout component for navigation and pagination in the documentation section. + +To use the component, import it in a [custom App](/custom-app). + +```jsx +import React from 'react' +import { SidebarLayout } from '@compositor/x0/components' + +export default props => + +``` + +## Customizing navigation + +The `props.routes` array can be altered to customize the order, names, and other aspects of the navigation. + +### Sorting the routes + +By default the `routes` array is in alphabetical order, with index pages occuring first. +To sort the array for display in navigation, pass a new `routes` prop to the SidebarLayout component. + +```jsx +import React from 'react' +import { SidebarLayout } from '@compositor/x0/components' +import sortBy from 'lodash.sortby' + +const navOrder = [ + 'index', + 'getting-started', + 'api' +] + +export default props => { + const sortedRoutes = sortBy(props.routes, route => { + const i = navOrder.indexOf(route.name) + return i + }) + + return ( + + ) +} +``` + +### Customizing Route Names + +By default the layout will format the filename by capitalizing each word and removing hyphens. +To customize the name of the routes for navigation, pass a new `routes` prop to the SidebarLayout component. + +```jsx +import React from 'react' +import { SidebarLayout } from '@compositor/x0/components' +import sortBy from 'lodash.sortby' + +const routeNames = { + index: 'Home', + api: 'API' +} + +export default props => { + const renamedRoutes = props.routes.map(route => { + if (!routeNames[route.name]) return route + return { + ...route, + name: routeNames[route.name] + } + }) + + return ( + + ) +} +``` + +## Full Width Pages + +The SidebarLayout component will center the contents of the page by default. +To make a page span the full width of the main column, set the `fullWidth` option in default props or front-matter. + +```md +--- +fullWidth: true +--- + +# Full-width markdown page +``` + +```jsx +import React from 'react' + +export default class extends React.Component { + static defaultProps = { + fullWidth: true + } + + render () { + return ( +

Full-width component

+ ) + } +} +``` + +## Page-Specific Layouts + +Custom layouts can be specified as front-matter or default props, then handled in a custom App component to control the layout for specific pages. + +```jsx +// example with custom layouts +import React from 'react' +import { SidebarLayout } from '@compositor/x0/components' +import HomeLayout from './_home-layout.js' + +export default props => { + const { route } = this.props + const { layout } = route.props + + const Layout = layout === 'home' ? HomeLayout : SidebarLayout + + return +} +``` + + diff --git a/docs/components/index.md b/docs/components/index.md new file mode 100644 index 0000000..cdbd4df --- /dev/null +++ b/docs/components/index.md @@ -0,0 +1,9 @@ + +# Built-in Components + +x0 includes several built-in components to make creating a custom site quicker. + +- [ScopeProvider](ScopeProvider) +- [SidebarLayout](SidebarLayout) +- [LivePreview](LivePreview) +- [LiveEditor](LiveEditor) diff --git a/docs/custom-app.md b/docs/custom-app.md new file mode 100644 index 0000000..ed80d10 --- /dev/null +++ b/docs/custom-app.md @@ -0,0 +1,126 @@ + +# Custom App Component + +Use a custom App component to completely customize the layout, add context providers, use global state, or set a custom scope. + +## Layouts + +Create a file named `_app.js` to provider a custom App component to x0. +This file can be used for custom layouts, including headers, footers, and navigation. + +```jsx +// _app.js +import React from 'react' +import { Link } from 'react-router-dom' +import { + Container, + Toolbar, + NavLink +} from 'rebass' + +export default class extends React.Component { + render () { + const { children } = this.props + return ( + + + + Home + + + + {children} + + + ) + } +} +``` + +## Providers + +Context providers, such as styled-component's `ThemeProvider` can be included in a custom app. + +```jsx +import React from 'react' +import { ThemeProvider } from 'styled-components' +import theme from '../src/theme' + +export default props => + + + {props.children} + + +``` + +## Scope + +Use the x0 `ScopeProvider` to customize the components used when rendering markdown elements in `.md` or `.mdx` files. +The `ScopeProvider` also provides scope to [live code examples](markdown/#code-fences) in code fences. + +```jsx +import React from 'react' +import { ScopeProvider } from '@compositor/x0/components' +import * as scope from '../src' + +export default props => + + {props.children} + +``` + +## App State + +Global application state can also be provided in a custom App. +Use `props.Component` instead of `props.children` to pass props to the rendered route. + +```jsx +import React from 'react' + +export default class extends React.Component { + state = { + count: 0 + } + + update = fn => this.setState(fn) + + render () { + const { Component } = this.props + + return ( + + ) + } +} +``` + +## Props + +Custom Apps receive the following props, which can expose greater control over the rendering. + +- `children`: rendered content of the page +- `Component`: a component to pass props to the current route and render content +- `routes`: an array of route objects for the entire site – can be used for rendering navigation +- `route`: the current route object +- The [React Router][react-router] state is also passed to the App + +### Route Object + +Routes include the following properties: + +- `key`: the filepath from webpack's `require.context` +- `name`: the basename of the file +- `path`: path used for routing +- `extname`: file extension +- `dirname`: file directory +- `exact`: (boolean) added to index pages for React Router +- `module`: the JS module for the file +- `Component`: the default export from the file +- `props`: default props or front-matter specified in the file + +[react-router]: https://github.com/ReactTraining/react-router + diff --git a/docs/examples.md b/docs/examples.md new file mode 100644 index 0000000..b3d0793 --- /dev/null +++ b/docs/examples.md @@ -0,0 +1,5 @@ +--- +ignore: true +--- + +# Examples diff --git a/docs/exporting.md b/docs/exporting.md new file mode 100644 index 0000000..4aa9152 --- /dev/null +++ b/docs/exporting.md @@ -0,0 +1,49 @@ + +# Exporting + +x0 sites can be exported as static sites using the `x0 build` command. + +```sh +x0 build docs +``` + +## Options + +Options for static export can be passed as flags or specified in a `package.json` field named `x0`. + +``` +-d --out-dir Output directory (default dist) +-s --static Output static HTML without JS bundle +-t --template Path to custom HTML template +--basename Basename for URL paths +--title Page title +``` + +## Custom HTML Templates + +A custom HTML template function can be used for greater control over the HTML output. + +```js +module.exports = ({ + html = '', + css = '', + scripts, + title = 'x0', + meta = [], + links = [], +}) => +` + + + + ${title} + ${css} + +
${html}
+${scripts} +` +``` + +See the [default template][template] for an example. + +[template]: https://github.com/c8r/x0/blob/master/lib/template.js diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..356939e --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,92 @@ +--- +--- + +# Getting Started + +Install x0 either globally or as a dev dependency in your project. + +```sh +npm install --global @compositor/x0 +``` + +```sh +npm install --save-dev @compositor/x0 +``` + +Create a directory for your documentation or other site. + +```sh +mkdir docs +``` + +Start the development server. + +```sh +x0 docs +``` + +*Note: if you installed x0 as a dev dependency, add the command above to a run script in your `package.json`* + + +Create an `index.md` file in the `docs/` directory. + +```md +# Hello World +``` + +Open your browser to to see the file you just created. + +To create another route, add another file to the `docs/` directory, +for example `getting-started.md` + +````md +# Getting Started + +```sh +npm install @compositor/x0 +``` +```` + +The `getting-started.md` file should now be available at . + +Add a link to the *Getting Started* page from `index.md`. + +```md +# Hello World + +- [Getting Started](getting-started) +``` + +## Using React components + +In addition to markdown, x0 can render any React component as a page. +Create a `demo.js` file. + +```jsx +// demo.js +import React from 'react' + +export default class extends React.Component { + render () { + return ( +

Demo

+ ) + } +} +``` + +## Using MDX + +x0 also supports [MDX][mdx] format, which allows you to mix JSX with markdown syntax. + +```md +import { Box } from 'rebass' + +# Hello MDX + + + This will render as a React component + +``` + +[mdx]: https://github.com/mdx-js/mdx diff --git a/docs/index.js b/docs/index.js index 0290f41..69c4155 100644 --- a/docs/index.js +++ b/docs/index.js @@ -20,8 +20,20 @@ const Video = styled.video([], { borderRadius: '16px', }) +const features = [ + 'Zero-config', + 'No plugins', + 'Components over configuration', + 'Use markdown, MDX, or React components', + 'Automatic file-system based routing', + 'Completely customizable', + 'Static-site generator', + 'Isolated development environment', +] + export default class extends React.Component { static defaultProps = { + name: 'Home', layout: 'landing' } @@ -29,15 +41,7 @@ export default class extends React.Component { return ( - - x0: Zero-config React development environment & static site generator - - + + + x0: + Document & develop React components without breaking a sweat +
npm i -g @compositor/x0
- */} + +
+ + + {features.map(feat => ( + + + {feat} + + + ))}
diff --git a/docs/markdown.md b/docs/markdown.md new file mode 100644 index 0000000..50048da --- /dev/null +++ b/docs/markdown.md @@ -0,0 +1,107 @@ +--- +title: 'x0: Using Markdown' +--- + +# Using Markdown + +Using standard markdown syntax for documentation means that your docs will be easy to edit +and render in many different markdown renderers, such as on [GitHub.com](https://github.com). + +In addition to the standard markdown syntax, x0 supports front matter and special fenced code blocks +that can render as live examples or previews of React components. + +## Links + +Standard markdown links work out of the box. +Under the hood, x0 converts relative links to React Router [`Link`][rr-link] components, +and absolute URLs are standard `` tags. + +```md +- [Link to another page](about) +- [Link to another site](http://example.com) +``` + +## Images + +Images in the same directory can be included with relative URLs, +but we recommend using a CDN and absolute URLs for any images. + +```md +![Hubble telescope image of a nebula](https://images.unsplash.com/photo-1462331940025-496dfbfc7564?w=2048&q=20) +``` + +Note: When using relative URLs, be sure to copy image assets to the dist folder when exporting a site with the `x0 build` command. + +## Code Fences + +To include a code snippet use a code fence. + +````md +```sh +npm install @compositor/x0 +``` +```` + +### Live Editor + +x0 includes support for special code fence language attributes for rendering live examples of React components. +Use the `.jsx` (note the `.` prefix) to render a live preview with an editable code editor. + +````md +```.jsx + +``` +```` + +The above code will render as the following (note this is only visible on the [documentation site][site]). +Try editing the JSX code below the preview. + +```.jsx + +``` + +Using code fences means that your example code will render in any standard markdown renderer, including GitHub. + +**Important Note**: To include custom components in scope for the live preview code fences, +you must use the `ScopeProvider` in a [custom App component](custom-app). + + +### Live Preview + +To render a component preview without the code editor below, use the `!jsx` (note the `!` prefix) language attibute. + +````md +```!jsx + +``` +```` + +The above code will render the following: + +```!jsx + +``` + +## Front Matter + +All `.md`, `.mdx`, and `.jsx` files in x0 support the use of [front-matter][fm] for setting default props and page-level metadata. + +```md +--- +title: Getting Started +--- +``` + +### Options + +Use the following front-matter options for controlling aspects of how a page renders. + +- `ignore`: when set to true, x0 will not add the file as a route + +#### HTML Template Options + +Front matter will be passed to the [HTML template](customizing) to allow control over the page title, metatags and more. + +[rr-link]: https://reacttraining.com/react-router/web/api/Link +[site]: https://compositor.io/x0/markdown +[fm]: https://github.com/jonschlinkert/gray-matter diff --git a/docs/react.md b/docs/react.md new file mode 100644 index 0000000..7c1e7ee --- /dev/null +++ b/docs/react.md @@ -0,0 +1,110 @@ + +# Using React Components + +In addition to markdown, x0 is optimized for rendering React components as pages. +This makes it work great as a highly customizable documentation generator, +or as a quick and minimal isolated development environment. + +To use a React component as a page, ensure the component is the `default` export. + +```jsx +import React from 'react' + +export default class extends React.Component { + render () { + return

Hello

+ } +} +``` + +## Default Props + +Default props on components work in a similar manner to front-matter, +allowing you to supply page-level metadata to the [HTML template](customizing). + +```jsx +import React from 'react' + +export default class extends React.Component { + static defaultProps = { + title: 'Hello' + } + + render () { + return

Hello

+ } +} +``` + +## Fetching Data + +Use the async `getInitialProps` static method to fetch data for the component. + +```jsx +import React from 'react' +import fetch from 'isomorphic-fetch' + +const endpoint = 'http://example.com/api' + +export default class extends React.Component { + static getInitialProps = async () => { + const data = await fetch(endpoint) + return { + data + } + } + + render () { + const { data } = this.props + + return
{JSON.stringify(data, null, 2)}
+ } +} + +``` + +## Local State + +Just like any React component, a page can use React state. + +```jsx +import React from 'react' + +export default class extends React.Component { + state = { + count: 0 + } + + increment = () => this.setState(state => ({ count: state.count + 1 })) + + render () { + return ( +
+ {count} + +
+ ) + } +} +``` + +## Links + +Import React Router's `Link` component to create links to other pages. + +```jsx +import React from 'react' +import { Link } from 'react-router-dom' + +export default props => +
+ Getting Started + API +
+``` + diff --git a/docs/routing.md b/docs/routing.md new file mode 100644 index 0000000..e678b70 --- /dev/null +++ b/docs/routing.md @@ -0,0 +1,57 @@ + +# Routing + +x0 automatically creates routes based on files in the root directory. +Any `.js`, `.md`, `.mdx`, or `.jsx` file will create a route based on the file name. + +*Note that `.jsx` files are JSX format and **not** standard JavaScript* + +Files that begin with an underscore (e.g. `_layout.js`) will be ignored. + +To create a page with a React component, it must be the `default` export of a module. + +```jsx +import React from 'react' + +export default class extends React.Component { + render () { + return ( +

Hello

+ ) + } +} +``` + +## Index routes + +Files with the `index` basename (e.g. `index.js` or `index.md`) will be used as an index page for the directory it's located in. Adding an `index.md` file at the root of the directory will make the page available at the `/` pathname in the URL. + +## Nested routes + +Adding files to subdirectories will create nested routes. +For example, adding a file at `api/core.md` will create a route at `/api/core` and a file at `api/index.md` will create a route for `/api/`. + +## Links + +x0 uses [React Router][react-router] under the hood. +In markdown, links will automatically use React Router's `` component for relative links. + +To add links in a React component, import the `Link` component from `react-router-dom` + +```jsx +import React from 'react' +import { Link } from 'react-router-dom' + +export default props => +
+ API +
+``` + +## 404 page + +A custom 404 page can be added with a file named `404.md`, `404.js`, `404.mdx`, or `404.jsx`. +By default, x0 will show a link list of available routes for URLs that aren't valid routes. +When exporting with `x0 build` the 404 page will be written to `404.html`, which works with GitHub pages. + +[react-router]: https://github.com/ReactTraining/react-router diff --git a/package.json b/package.json index f2a3325..a1b8ba7 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "x0": "cli.js" }, "scripts": { - "start": "./cli.js docs -p 8989", + "start": "./cli.js docs", "build": "./cli.js build docs", "test": "nyc ava --timeout=60s", "test:components": "nyc ava test/components.js", diff --git a/src/SidebarLayout.js b/src/SidebarLayout.js index 3559aea..e1a757b 100644 --- a/src/SidebarLayout.js +++ b/src/SidebarLayout.js @@ -267,7 +267,7 @@ export default class Layout extends React.Component { ? React.Fragment : MaxWidth - const index = routes.findIndex(r => r === route) + const index = routes.findIndex(r => r.path === route.path) const pagination = { previous: routes[index - 1], next: routes[index + 1] diff --git a/src/entry.js b/src/entry.js index dc07be8..5c76d75 100644 --- a/src/entry.js +++ b/src/entry.js @@ -98,7 +98,7 @@ export const getRoutes = async (components = initialComponents) => { const RouterState = withRouter(({ render, ...props }) => { const { pathname } = props.location - const route = props.routes.find(r => r.path === pathname || r.href === pathname) + const route = props.routes.find(r => r.path === pathname || r.href === pathname) || { props: {} } return render({ ...props, route }) })