diff --git a/.gitignore b/.gitignore
index 5dadb79..a4c28e4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@ coverage
.nyc_output
test/output
TMP
+dist
diff --git a/.npmignore b/.npmignore
index a545358..c755bc6 100644
--- a/.npmignore
+++ b/.npmignore
@@ -1,6 +1,10 @@
TMP
coverage
.nyc_output
+test
test/output
docs
examples
+demo
+dist
+.travis.yml
diff --git a/.travis.yml b/.travis.yml
index 16e60e4..1adc919 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,3 +1,13 @@
language: node_js
node_js:
- - 8.9
+ - 10.0
+before_deploy:
+ - npm install
+ - npm run build
+deploy:
+ provider: pages
+ skip_cleanup: true
+ github_token: $GH_TOKEN
+ local_dir: dist
+ on:
+ branch: master
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..233d6f5
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,45 @@
+
+# Changelog
+
+## 5.0.0
+
+- x0 now accepts a folder of components as the entry argument
+- Automatic routing based on filename
+- Dev server uses [webpack-serve](https://github.com/webpack-contrib/webpack-serve) under the hood
+- Uses [mini-html-webpack-plugin](https://github.com/styleguidist/mini-html-webpack-plugin)
+- Default HTML head contents for UTF-8 charset and viewport meta tag
+- Minimal base CSS styling
+- Rendering the `
` in the component is no longer supported
+- Webpack is used both for the client and static rendering, enabling webpack features in `getInitialProps`
+- Support for [babel-plugin-macros](https://github.com/kentcdodds/babel-plugin-macros)
+- Default props can no longer be passed through the `package.json` config
+- The `routes` array in `package.json` is no longer supported
+- Adding [react-router](https://github.com/ReactTraining/react-router) is no longer necessary
+- Removes [react-loadable](https://github.com/jamiebuilds/react-loadable) support
+- Proxy option is no longer supported, but can be configured with a custom webpack config
+- Automatically looks for a `webpack.config.js` file in the directory
+- The `--config` flag has been renamed to `--webpack`
+- Automatic support for [styled-components](https://github.com/styled-components/styled-components)
+- Automatic support for [emotion](https://github.com/emotion-js/emotion)
+- Custom HTML template option
+- Supports custom App component to wrap all routes
+- Support for [JSX](https://github.com/c8r/jsx-loader) file format
+- Support for [MDX](https://github.com/mdx-js/mdx) file format
+
+### Migrating from v4
+
+- A directory should be passed to the x0 command, instead of a single file
+- React router is not necessary for routing
+- The `routes` option is no longer supported
+- HTML head contents should be removed from components
+- Viewport and charset meta tags are included by default
+- Use the custom template option or head options to populate HTML head contents
+- Default props set in options should be added to the components
+- Custom usage of react-loadable will require additional setup
+- The `--config` flag should be renamed to `--webpack`
+- The `proxy` option is no longer supported
+- The `cssLibrary` option is no longer required
+- Support for automatic static rendering with `glamor`, `glamorous`, and `fela` is no longer supported
+- The `getInitialProps` method's `pathname` argument has be renamed to `path`
+- The `getInitialProps` method *only* receives the `path` argument, all other arguments are deprecated
+
diff --git a/README.md b/README.md
index 5966740..e03b9c3 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,7 @@
# x0
Zero-config React development environment and static site generator
+
[![Build Status][build-badge]][build]
```sh
@@ -11,7 +12,7 @@ npm install -g @compositor/x0
[build-badge]: https://img.shields.io/travis/c8r/x0/master.svg?style=flat-square
[build]: https://travis-ci.org/c8r/x0
-![screen-demo](docs/demo.gif)
+
## Features
@@ -19,12 +20,11 @@ npm install -g @compositor/x0
- Hot-loading development environment
- Works with virtually any React component\*
- No confusing APIs
-- Renders static HTML
-- Renders JS bundles
-- Works with CSS-in-JS libraries like [styled-components][sc] and [glamorous][glamorous]
-- Support for routing with [react-router][react-router]
+- Automatic file system based routing
+- Exports static HTML
+- Exports JS bundles
+- Works with CSS-in-JS libraries like [styled-components][sc] and [emotion][emotion]
- Support for async data fetching
-- Support for code splitting with [React Loadable][react-loadable]
\* Custom [webpack configuration](#webpack) is required for components that rely on webpack-based features
@@ -32,56 +32,58 @@ npm install -g @compositor/x0
## Isolated development environment
```sh
-x0 src/App.js
+x0 components
```
Options:
```
- -o --open Open dev server in default browser
- -p --port Set custom port for dev server
+-o --open Open dev server in default browser
+-p --port Custom port for dev server
+-t --template Path to custom HTML template
+--webpack Path to custom webpack configuration
```
```sh
-x0 src/App.js -op 8080
+x0 components -op 8080
```
-## Static Render
+## Static Build
-Render static HTML and client-side bundle
+Export static HTML and client-side bundle
```sh
-x0 build src/App.js --out-dir site
+x0 build components
```
-Render static HTML without bundle
+Export static HTML without bundle
```sh
-x0 build src/App.js --out-dir site --static
+x0 build components --static
```
Options
```
- -d --out-dir Directory to save index.html and bundle.js to
- -s --static Only render static HTML (no client-side JS)
+-d --out-dir Output directory (default dist)
+-s --static Output static HTML without JS bundle
+-t --template Path to custom HTML template
+--webpack Path to custom webpack configuration
```
+
## Fetching Data
-Use the `getInitialProps` static method to fetch data for static rendering.
-This method was inspired by [Next.js][nextjs] but only works for static rendering.
+Use the async `getInitialProps` static method to fetch data for static rendering.
+This method was inspired by [Next.js][nextjs].
```jsx
-const App = props => (
+const Index = props => (
Hello {props.data}
)
-App.getInitialProps = async ({
- Component,
- pathname
-}) => {
+Index.getInitialProps = async () => {
const fetch = require('isomorphic-fetch')
const res = await fetch('http://example.com/data')
const data = await res.json()
@@ -90,147 +92,224 @@ App.getInitialProps = async ({
}
```
-## CSS-in-JS
+## Custom App
-x0 supports server-side rendering for [styled-components][sc], [glamor][glamor], [glamorous][glamorous], and [fela][fela].
-To enable CSS rendering for static output, use the `cssLibrary` option
+A custom `App` component can be provided by including an `_app.js` file.
+The `App` component uses the [render props][render-props] pattern to provide additional state and props to its child routes.
-```sh
-x0 build src/App.js --cssLibrary="styled-components"
-```
+[render-props]: https://reactjs.org/docs/render-props.html
-Available options:
+```jsx
+// example _app.js
+import React from 'react'
-- [`styled-components`][sc]
-- [`glamorous`][glamorous]
-- [`glamor`][glamor]
-- [`fela`][fela]
+export default class extends React.Component {
+ state = {
+ count: 0
+ }
-## Head content
+ update = fn => this.setState(fn)
+
+ render () {
+ const { render, routes } = this.props
-Head elements such as ``, ` `, and `
npm install @compositor/x0 Get Started
Read the docs and get started on GitHub or sign up for updates.
Documentation
\ No newline at end of file
diff --git a/docs/index.js b/docs/index.js
new file mode 100644
index 0000000..52d8dc5
--- /dev/null
+++ b/docs/index.js
@@ -0,0 +1,70 @@
+import React from 'react'
+import { Link } from 'react-router-dom'
+import styled from 'styled-components'
+import {
+ Container,
+ Box,
+ Flex,
+ Caps,
+ Heading,
+ Text,
+ Button,
+ Pre,
+} from 'rebass'
+import { Logo } from '@compositor/logo'
+
+const Video = styled.video([], {
+ display: 'block',
+ maxWidth: '100%',
+ height: 'auto',
+ borderRadius: '16px',
+})
+
+export default class extends React.Component {
+ render () {
+ return (
+
+
+
+ x0: Zero-config React development environment & static site generator
+
+
+
+
+ npm i -g @compositor/x0
+
+
+ GitHub
+
+
+
+ Documentation
+
+
+
+
+ )
+ }
+}
diff --git a/docs/theme.js b/docs/theme.js
new file mode 100644
index 0000000..4ef59b4
--- /dev/null
+++ b/docs/theme.js
@@ -0,0 +1,8 @@
+import { theme } from 'rebass'
+
+export default {
+ ...theme,
+ radii: [
+ 0, 4, 8, 16
+ ]
+}
diff --git a/docs/webpack.config.js b/docs/webpack.config.js
index 884b38c..9718488 100644
--- a/docs/webpack.config.js
+++ b/docs/webpack.config.js
@@ -3,7 +3,20 @@ module.exports = {
rules: [
{
test: /\.md$/,
- use: 'raw-loader'
+ use: [
+ // 'raw-loader'
+ {
+ loader: 'babel-loader',
+ options: {
+ presets: [
+ 'env',
+ 'stage-0',
+ 'react'
+ ]
+ }
+ },
+ '@compositor/md-loader'
+ ]
}
]
}
diff --git a/examples/bundle/App.js b/examples/bundle/App.js
index e2630e8..e218191 100644
--- a/examples/bundle/App.js
+++ b/examples/bundle/App.js
@@ -1,5 +1,5 @@
import React from 'react'
-import connect from 'refunk'
+import { connect } from 'refunk'
const dec = state => ({ count: state.count - 1 })
const inc = state => ({ count: state.count + 1 })
diff --git a/examples/styled-components/App.js b/examples/styled-components/App.js
index c5f571e..6f7f147 100644
--- a/examples/styled-components/App.js
+++ b/examples/styled-components/App.js
@@ -1,6 +1,6 @@
import React from 'react'
import styled, { ServerStyleSheet } from 'styled-components'
-import connect from 'refunk'
+import { connect } from 'refunk'
const css = `*{box-sizing:border-box}
body{font-family:-apple-system,BlinkMacSystemFont,sans-serif;margin:0}`
diff --git a/index.js b/index.js
index ce914ea..837fcd8 100644
--- a/index.js
+++ b/index.js
@@ -1 +1,2 @@
-module.exports = require('./lib')
+module.exports.dev = require('./lib/dev')
+module.exports.build = require('./lib/build')
diff --git a/lib/build.js b/lib/build.js
new file mode 100644
index 0000000..fef193d
--- /dev/null
+++ b/lib/build.js
@@ -0,0 +1,214 @@
+const fs = require('fs-extra')
+const path = require('path')
+const webpack = require('webpack')
+const MiniHTMLWebpackPlugin = require('mini-html-webpack-plugin')
+const { generateJSReferences } = require('mini-html-webpack-plugin')
+const merge = require('webpack-merge')
+const React = require('react')
+const { renderToString, renderToStaticMarkup } = require('react-dom/server')
+const { StaticRouter } = require('react-router-dom')
+const semver = require('semver')
+
+const util = require('util')
+
+const baseConfig = require('./config')
+const createTemplate = require('./createTemplate')
+
+const getApp = opts => {
+ const config = merge(baseConfig, opts.webpack)
+
+ config.mode = 'development'
+ config.entry = path.join(__dirname, './entry.js')
+ config.output= {
+ path: opts.tempdir,
+ filename: 'App.js',
+ libraryTarget: 'umd'
+ }
+ config.target = 'node'
+
+ const compiler = webpack(config)
+
+ return new Promise((resolve, reject) => {
+ compiler.run((err, stats) => {
+ if (err) {
+ reject(err)
+ return
+ }
+ const App = require(
+ path.resolve(opts.tempdir, './App.js')
+ )
+ resolve(App)
+ })
+ })
+}
+
+const STYLED_COMPONENTS_VERSION = '>=3.0'
+const EMOTION_VERSION = '>=9.0'
+const GLAMOR_VERSION = '>=2.0'
+
+const getCSSLibrary = opts => {
+ if (opts.cssLibrary) return opts.cssLibrary
+ if (!opts.pkg) return null
+ const deps = Object.assign({},
+ opts.pkg.devDependencies,
+ opts.pkg.dependencies
+ )
+ if (deps['styled-components']) {
+ const scVersion = semver.coerce(deps['styled-components'])
+ if (!semver.satisfies(scVersion, STYLED_COMPONENTS_VERSION)) return null
+ return 'styled-components'
+ }
+ if (deps.emotion) {
+ const emotionVersion = semver.coerce(deps.emotion)
+ if (!semver.satisfies(emotionVersion, EMOTION_VERSION)) return null
+ return 'emotion'
+ }
+ if (deps.glamor) { // || deps.glamorous) {
+ const glamorVersion = semver.coerce(deps.glamor)
+ if (!semver.satisfies(glamorVersion, GLAMOR_VERSION)) return null
+ return 'glamor'
+ }
+ return null
+}
+
+const renderHTML = ({
+ opts,
+ routes,
+ App,
+ props,
+ path
+}) => {
+ const render = opts.static ? renderToStaticMarkup : renderToString
+ const cssLibrary = getCSSLibrary(opts)
+ const app = React.createElement(App.default, { routes, path })
+ let html
+ let css
+ switch (cssLibrary) {
+ case 'styled-components':
+ const { ServerStyleSheet } = require('styled-components')
+ const sheet = new ServerStyleSheet()
+ html = render(
+ sheet.collectStyles(
+ React.createElement(App.default, { routes, path })
+ )
+ )
+ css = sheet.getStyleTags()
+ return { path, html, css, props }
+ case 'emotion':
+ const { renderStylesToString } = require('emotion-server')
+ html = renderStylesToString(
+ render(app)
+ )
+ return { path, html, props }
+ case 'glamor':
+ // doesn't seem to be working...
+ const glamor = require('glamor/server')
+ const res = glamor.renderStatic(() => (
+ render(app)
+ ))
+ html = res.html
+ css = ``
+ return { path, html, css, props }
+ default:
+ html = render(app)
+ return { path, html, props }
+ }
+}
+
+const remove = filename => {
+ fs.remove(filename, err => {
+ if (err) console.log(err)
+ })
+}
+
+const getRoutes = async (App) => {
+ const routes = await App.getRoutes()
+
+ const dynamicRoutes = []
+ routes.forEach(route => {
+ if (route.props.routes) {
+ route.props.routes.forEach(subroute => dynamicRoutes.push(
+ Object.assign({}, route, subroute)
+ ))
+ }
+ })
+ const staticRoutes = [
+ ...routes.filter(route => !route.props.routes),
+ ...dynamicRoutes
+ ]
+ return { routes, staticRoutes }
+}
+
+module.exports = async (opts) => {
+ // mutation
+ baseConfig.resolve.modules.unshift(
+ path.join(opts.dirname, 'node_modules'),
+ opts.dirname
+ )
+
+ // mutation
+ baseConfig.plugins.push(
+ new webpack.DefinePlugin({
+ DEV: JSON.stringify(false),
+ OPTIONS: JSON.stringify(opts),
+ DIRNAME: JSON.stringify(opts.dirname),
+ APP: JSON.stringify(opts.app)
+ })
+ )
+
+ opts.tempdir = path.join(opts.outDir, 'TEMP')
+ if (!fs.existsSync(opts.outDir)) fs.mkdirSync(opts.outDir)
+ if (!fs.existsSync(opts.tempdir)) fs.mkdirSync(opts.tempdir)
+
+ const App = await getApp(opts)
+ const { routes, staticRoutes } = await getRoutes(App)
+ const template = createTemplate(opts)
+
+ const pages = staticRoutes.map(route => renderHTML(
+ Object.assign({}, route, {
+ opts,
+ App,
+ routes,
+ })
+ ))
+
+ const config = merge(baseConfig, opts.webpack)
+
+ config.mode = opts.debug ? 'development' : 'production'
+ if (opts.debug) {
+ config.stats = 'verbose'
+ }
+ config.entry = path.join(__dirname, './entry')
+ config.output = {
+ path: opts.outDir,
+ filename: 'bundle.js',
+ publicPath: (opts.basename || '') + '/'
+ }
+
+ // push per route/page
+ pages.forEach(({ path, html, css, props }) => {
+ config.plugins.push(
+ new MiniHTMLWebpackPlugin({
+ filename: path + '/index.html',
+ context: Object.assign({}, opts, props, { html, css }),
+ template
+ })
+ )
+ })
+ const compiler = webpack(config)
+
+ return new Promise((resolve, reject) => {
+ compiler.run((err, stats) => {
+ if (err) {
+ reject(err)
+ return
+ }
+ remove(opts.tempdir)
+ if (opts.static) {
+ const bundle = path.join(opts.outDir, 'bundle.js')
+ remove(bundle)
+ }
+ resolve(stats)
+ })
+ })
+}
diff --git a/lib/config.js b/lib/config.js
new file mode 100644
index 0000000..c2039d9
--- /dev/null
+++ b/lib/config.js
@@ -0,0 +1,65 @@
+const path = require('path')
+
+const babel = {
+ presets: [
+ 'babel-preset-env',
+ 'babel-preset-stage-0',
+ 'babel-preset-react',
+ ].map(require.resolve),
+ plugins: [
+ 'babel-plugin-macros',
+ 'babel-plugin-transform-runtime'
+ ].map(require.resolve)
+}
+
+const rules = [
+ {
+ test: /\.js$/,
+ exclude: /node_modules/,
+ loader: require.resolve('babel-loader'),
+ options: babel
+ },
+ {
+ test: /\.js$/,
+ exclude: path.resolve(__dirname, '../node_modules'),
+ include: path.resolve(__dirname),
+ loader: require.resolve('babel-loader'),
+ options: babel
+ },
+ {
+ test: /\.jsx$/,
+ loader: require.resolve('@compositor/jsx-loader'),
+ options: {}
+ },
+ {
+ test: /\.mdx$/,
+ use: [
+ {
+ loader: require.resolve('babel-loader'),
+ options: babel
+ },
+ {
+ loader: require.resolve('@mdx-js/loader')
+ }
+ ]
+ }
+]
+
+// common config
+module.exports = {
+ stats: 'none',
+ resolve: {
+ modules: [
+ __dirname,
+ path.join(__dirname, '../node_modules'),
+ 'node_modules'
+ ]
+ },
+ module: {
+ rules
+ },
+ node: {
+ fs: 'empty'
+ },
+ plugins: []
+}
diff --git a/lib/createTemplate.js b/lib/createTemplate.js
new file mode 100644
index 0000000..efa38bb
--- /dev/null
+++ b/lib/createTemplate.js
@@ -0,0 +1,18 @@
+const { generateJSReferences } = require('mini-html-webpack-plugin')
+const { minify } = require('html-minifier')
+const defaultTemplate = require('./template')
+
+module.exports = opts => {
+ const template = opts.template || defaultTemplate
+ return context => {
+ const scripts = generateJSReferences(context.js, context.publicPath)
+ return minify(
+ template(Object.assign({}, context, {
+ scripts
+ })),
+ {
+ collapseWhitespace: true
+ }
+ )
+ }
+}
diff --git a/lib/dev.js b/lib/dev.js
new file mode 100644
index 0000000..0363ea0
--- /dev/null
+++ b/lib/dev.js
@@ -0,0 +1,78 @@
+const path = require('path')
+const webpack = require('webpack')
+const serve = require('webpack-serve')
+const history = require('connect-history-api-fallback')
+const convert = require('koa-connect')
+const MiniHTMLWebpackPlugin = require('mini-html-webpack-plugin')
+const merge = require('webpack-merge')
+
+const baseConfig = require('./config')
+const createTemplate = require('./createTemplate')
+
+const dev = {
+ hot: true,
+ logLevel: 'error',
+ clientLogLevel: 'none',
+ stats: 'errors-only'
+}
+
+module.exports = async (opts) => {
+ const config = merge(baseConfig, opts.webpack)
+ const template = createTemplate(opts)
+
+ config.mode = 'development'
+ config.context = opts.dirname
+ config.entry = path.join(__dirname, './entry')
+ config.output= {
+ path: path.join(process.cwd(), 'dev'),
+ filename: 'dev.js',
+ publicPath: '/'
+ }
+
+ config.resolve.modules.unshift(
+ opts.dirname,
+ path.join(opts.dirname, 'node_modules')
+ )
+
+ config.plugins.push(
+ new webpack.DefinePlugin({
+ DEV: JSON.stringify(true),
+ OPTIONS: JSON.stringify(opts),
+ DIRNAME: JSON.stringify(opts.dirname),
+ APP: JSON.stringify(opts.app),
+ })
+ )
+
+ config.plugins.push(
+ new MiniHTMLWebpackPlugin({
+ context: opts,
+ template
+ })
+ )
+
+ if (opts.debug) {
+ config.stats = 'verbose'
+ // todo: enable other logging
+ }
+
+ const serveOpts = {
+ config,
+ dev,
+ logLevel: 'error',
+ port: opts.port,
+ hot: { logLevel: 'error' },
+ add: (app, middleware, options) => {
+ app.use(convert(history({})))
+ }
+ }
+
+ return new Promise((resolve, reject) => {
+ serve(serveOpts)
+ .then(server => {
+ server.compiler.hooks.done.tap({ name: 'x0' }, (stats) => {
+ resolve({ server, stats })
+ })
+ })
+ .catch(reject)
+ })
+}
diff --git a/lib/dev/App.js b/lib/dev/App.js
deleted file mode 100644
index 6691e50..0000000
--- a/lib/dev/App.js
+++ /dev/null
@@ -1,23 +0,0 @@
-const React = require('react')
-const h = React.createElement
-const Catch = require('./Catch')
-
-class App extends React.Component {
- constructor (props) {
- super(props)
-
- this.state = {
- Component: props.Component,
- }
- }
-
- render () {
- const { Component } = this.state
-
- return h(Catch, null,
- h(Component, this.props)
- )
- }
-}
-
-module.exports = App
diff --git a/lib/dev/Catch.js b/lib/dev/Catch.js
deleted file mode 100644
index 5ab0ba6..0000000
--- a/lib/dev/Catch.js
+++ /dev/null
@@ -1,38 +0,0 @@
-const React = require('react')
-
-class Catch extends React.Component {
- constructor () {
- super()
-
- this.state = {
- err: null
- }
- }
-
- componentDidCatch (err) {
- console.error(err)
- this.setState({ err })
- }
-
- componentWillReceiveProps (next) {
- this.setState({ err: null })
- }
-
- render () {
- if (this.state.err) {
- return React.createElement('pre', {
- style: {
- fontFamily: '"Roboto Mono", Menlo, monospace',
- whiteSpace: 'pre-wrap',
- padding: 32,
- color: 'white',
- backgroundColor: 'red'
- }
- }, this.state.err.toString())
- }
-
- return React.Children.only(this.props.children)
- }
-}
-
-module.exports = Catch
diff --git a/lib/dev/config.js b/lib/dev/config.js
deleted file mode 100644
index e2f779d..0000000
--- a/lib/dev/config.js
+++ /dev/null
@@ -1,49 +0,0 @@
-const path = require('path')
-const webpack = require('webpack')
-
-// dev webpack config
-module.exports = {
- mode: 'development',
- devtool: 'source-map',
- entry: [
- path.join(__dirname, './entry'),
- ],
- output: {
- path: __dirname,
- filename: 'dev.js',
- publicPath: '/'
- },
- resolve: {
- modules: [
- path.join(__dirname, '../../node_modules'),
- 'node_modules'
- ]
- },
- module: {
- rules: [
- {
- test: /\.js?$/,
- exclude: /node_modules/,
- use: {
- loader: require.resolve('babel-loader'),
- options: {
- presets: [
- 'babel-preset-env',
- 'babel-preset-stage-0',
- 'babel-preset-react'
- ].map(require.resolve),
- plugins: [
- require.resolve('babel-plugin-transform-runtime')
- ]
- }
- }
- }
- ]
- },
- node: {
- fs: 'empty'
- },
- plugins: [
- new webpack.HotModuleReplacementPlugin()
- ]
-}
diff --git a/lib/dev/entry.js b/lib/dev/entry.js
deleted file mode 100644
index 17493fc..0000000
--- a/lib/dev/entry.js
+++ /dev/null
@@ -1,26 +0,0 @@
-const React = require('react')
-const { render } = require('react-dom')
-const App = require('./App')
-
-const div = typeof APP !== 'undefined' ? APP : document.body
-
-const id = require.resolve(COMPONENT)
-const req = require(COMPONENT)
-const Component = req.default || req
-
-const props = Object.assign({
- id,
- Component
-}, PROPS)
-
-const app = render(React.createElement(App, props), div)
-
-if (module.hot) {
- module.hot.accept(id, function () {
- const next = require(COMPONENT)
- const NextComponent = next.default || next
- app.setState({
- Component: NextComponent
- })
- })
-}
diff --git a/lib/dev/index.js b/lib/dev/index.js
deleted file mode 100644
index 794681e..0000000
--- a/lib/dev/index.js
+++ /dev/null
@@ -1,109 +0,0 @@
-require('babel-register')({
- presets: [
- [ require.resolve('babel-preset-env'), {
- targets: {
- node: '8'
- }
- }],
- require.resolve('babel-preset-stage-0'),
- require.resolve('babel-preset-react')
- ],
- plugins: [
- 'react-loadable/babel',
- 'babel-plugin-syntax-dynamic-import',
- 'babel-plugin-dynamic-import-node',
- ].map(require.resolve)
-})
-const path = require('path')
-const webpack = require('webpack')
-const DevServer = require('webpack-dev-server')
-const merge = require('webpack-merge')
-const config = require('./config')
-
-const getWebpackApp = require('../static/webpackNodeApp')
-
-const devOptions = {
- hot: true,
- historyApiFallback: {
- index: '/dev'
- },
- overlay: true
-}
-
-const start = async (filename, options, config) => {
- const Component = await getWebpackApp(filename, options)
-
- const {
- proxy,
- port = 8000
- } = options
-
- const getProps = typeof Component.getInitialProps === 'function'
- ? Component.getInitialProps
- : async () => null
-
- const initialProps = await getProps(Object.assign({
- Component
- }, options))
-
- const props = Object.assign({}, options, initialProps)
-
- const defs = new webpack.DefinePlugin({
- COMPONENT: JSON.stringify(filename),
- PROPS: JSON.stringify(props)
- })
-
- config.plugins.push(defs)
-
- devOptions.contentBase = path.dirname(filename)
-
- if (proxy) {
- devOptions.proxy = proxy
- }
-
- const compiler = webpack(config)
- const server = new DevServer(compiler, devOptions)
-
- return new Promise((resolve, reject) => {
- compiler.plugin('done', () => {
- resolve(server)
- })
-
- server.listen(port, err => {
- if (err) throw err
- })
- })
-}
-
-module.exports = (filename, options = {}) => {
- if (!filename) return
- const dirname = path.dirname(filename)
-
- const {
- port = 8000
- } = options
-
- config.resolve.modules.unshift(
- dirname,
- path.join(process.cwd(), 'node_modules'),
- path.join(dirname, 'node_modules'),
- )
-
- config.entry.push(
- `webpack-dev-server/client?http://localhost:${port}`,
- 'webpack/hot/only-dev-server'
- )
-
- let mergedConfig = config
-
- if (options.basename) {
- config.output.publicPath = options.basename + '/'
- }
-
- if (options.config) {
- const userConfig = require(options.config)
- mergedConfig = merge(config, userConfig)
- }
-
- return start(filename, options, mergedConfig)
-}
diff --git a/lib/entry.js b/lib/entry.js
new file mode 100644
index 0000000..41eb723
--- /dev/null
+++ b/lib/entry.js
@@ -0,0 +1,194 @@
+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'
+
+const IS_CLIENT = typeof document !== 'undefined'
+const req = require.context(DIRNAME, false, /\.(js|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)
+}))
+ .filter(component => !/^(\.|_)/.test(component.name))
+ .filter(component => typeof component.Component === 'function')
+
+const initialComponents = getComponents(req)
+
+const Index = ({ routes = [] }) => (
+
+ {DIRNAME}
+
+ {routes.map(route => (
+
+
+ {route.name}
+
+
+ ))}
+
+
+)
+
+const DefaultApp = ({ render, routes }) => (
+
+ {render()}
+ (
+
+ )} />
+
+)
+
+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 (
+
+ )
+ }
+
+ 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)
+
+export const getRoutes = async (components = initialComponents) => {
+ const routes = await components.map(async ({ key, name, module, Component }) => {
+ const exact = name === index
+ let pathname = exact ? '/' : '/' + name
+ const props = Component.getInitialProps
+ ? await Component.getInitialProps({ path: pathname })
+ : {}
+ pathname = props.path || pathname
+ return {
+ key: name,
+ name,
+ path: pathname,
+ exact,
+ module,
+ Component,
+ props
+ }
+ })
+ return Promise.all(routes)
+}
+
+export default class Root extends React.Component {
+ static defaultProps = {
+ path: '/',
+ basename
+ }
+ state = this.props
+
+ render () {
+ const {
+ routes,
+ basename,
+ path = '/'
+ } = this.state
+
+ return (
+
+
+
+ (
+ routes.map(({ Component, ...route }) => (
+ (
+
+
+
+ )}
+ />
+ ))
+ )}
+ />
+
+ {!disableScroll && }
+
+
+ )
+ }
+}
+
+let app
+if (IS_CLIENT) {
+ const mount = DEV ? render : hydrate
+ const div = window.root || document.body.appendChild(
+ document.createElement('div')
+ )
+ getRoutes()
+ .then(routes => {
+ app = mount( , div)
+ })
+}
+
+if (IS_CLIENT && module.hot) {
+ module.hot.accept()
+}
+
diff --git a/lib/index.js b/lib/index.js
deleted file mode 100644
index 31373ca..0000000
--- a/lib/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-module.exports.dev = require('./dev')
-module.exports.static = require('./static')
diff --git a/lib/static/client.js b/lib/static/client.js
deleted file mode 100644
index 6f44aaa..0000000
--- a/lib/static/client.js
+++ /dev/null
@@ -1,96 +0,0 @@
-const path = require('path')
-const webpack = require('webpack')
-const MinifyPlugin = require('babel-minify-webpack-plugin')
-const { ReactLoadablePlugin } = require('react-loadable/webpack')
-const merge = require('webpack-merge')
-
-const config = {
- mode: 'production',
- entry: [
- path.join(__dirname, './entry')
- ],
- output: {
- filename: 'bundle.js',
- publicPath: '/'
- },
- resolve: {
- modules: [
- path.join(__dirname, '../../node_modules'),
- 'node_modules'
- ]
- },
- module: {
- rules: [
- {
- test: /\.js$/,
- exclude: /node_modules/,
- use: {
- loader: require.resolve('babel-loader'),
- options: {
- presets: [
- 'babel-preset-env',
- 'babel-preset-stage-0',
- 'babel-preset-react'
- ].map(require.resolve),
- plugins: [
- require.resolve('babel-plugin-transform-runtime')
- ]
- }
- }
- }
- ]
- },
- node: {
- fs: 'empty'
- },
- plugins: [
- new ReactLoadablePlugin({
- filename: path.join(__dirname, './TMP/react-loadable.json'),
- })
- ]
-}
-
-module.exports = (filename, options = {}) => {
- if (options.static || !options.outDir) return
-
- const dirname = path.dirname(filename)
-
- config.output.path = path.join(process.cwd(), options.outDir)
-
- if (options.basename) {
- config.output.publicPath = options.basename + '/'
- }
-
- config.resolve.modules.unshift(
- dirname,
- path.join(process.cwd(), 'node_modules'),
- path.join(dirname, 'node_modules')
- )
-
- config.plugins.push(
- new webpack.DefinePlugin({
- 'process.env': {
- NODE_ENV: JSON.stringify('production')
- },
- COMPONENT: JSON.stringify(filename)
- })
- )
-
- let mergedConfig = config
- if (options.config) {
- const userConfig = require(options.config)
- mergedConfig = merge(config, userConfig)
- }
-
- const compiler = webpack(mergedConfig)
-
- return new Promise((resolve, reject) => {
- compiler.run((err, stats) => {
- if (err) {
- console.log(err)
- reject(err)
- }
- resolve(stats)
- })
- })
-}
diff --git a/lib/static/entry.js b/lib/static/entry.js
deleted file mode 100644
index c88ffcf..0000000
--- a/lib/static/entry.js
+++ /dev/null
@@ -1,16 +0,0 @@
-const React = require('react')
-const { hydrate } = require('react-dom')
-const Loadable = require('react-loadable')
-
-const App = require(COMPONENT).default || require(COMPONENT)
-const data = document.getElementById('__initial-props__').innerHTML
-const props = JSON.parse(data)
-const div = document.documentElement
-
-Loadable.preloadReady()
- .then(() => {
- hydrate(
- React.createElement(App, props),
- div
- )
- })
diff --git a/lib/static/getCSS.js b/lib/static/getCSS.js
deleted file mode 100644
index 9c32c25..0000000
--- a/lib/static/getCSS.js
+++ /dev/null
@@ -1,59 +0,0 @@
-const React = require('react')
-const { renderToString } = require('react-dom/server')
-
-const getSC = (Component, props) => {
- const SC = require('styled-components')
- const sheet = new SC.ServerStyleSheet()
- renderToString(
- sheet.collectStyles(
- React.createElement(Component, props)
- )
- )
- const tags = sheet.getStyleTags()
- return tags
-}
-
-const getGlamor = (Component, props) => {
- const glamor = require('glamor/server')
- const { css } = glamor.renderStatic(() => (
- renderToString(
- React.createElement(Component, props)
- )
- ))
- const tag = ``
- return tag
-}
-
-const getFela = (Component, props) => {
- if (!props.renderer) {
- console.log('Warning: Fela static rendering requires a `renderer` to be passed through the `getInitialProps()` method.')
- return ''
- }
- const fela = require('fela')
- const felaDOM = require('fela-dom')
- const renderer = props.renderer || fela.createRenderer()
- renderToString(
- React.createElement(Component, props)
- )
- const tag = felaDOM.renderToMarkup(renderer)
- return tag
-}
-
-const libraries = {
- 'styled-components': getSC,
- glamorous: getGlamor,
- glamor: getGlamor,
- fela: getFela,
-}
-
-const noop = () => ''
-
-module.exports = (Component, props = {}) => {
- const { cssLibrary } = props
- const getCSS = libraries[cssLibrary] || noop
-
- // style tag strings
- const css = getCSS(Component, props)
-
- return css
-}
diff --git a/lib/static/index.js b/lib/static/index.js
deleted file mode 100644
index ccf0660..0000000
--- a/lib/static/index.js
+++ /dev/null
@@ -1,136 +0,0 @@
-require('babel-register')({
- presets: [
- [ require.resolve('babel-preset-env'), {
- targets: {
- node: '8'
- }
- }],
- require.resolve('babel-preset-stage-0'),
- require.resolve('babel-preset-react')
- ],
- plugins: [
- 'react-loadable/babel',
- 'babel-plugin-syntax-dynamic-import',
- 'babel-plugin-dynamic-import-node',
- ].map(require.resolve)
-})
-
-const fs = require('fs')
-const path = require('path')
-const React = require('react')
-const { renderToString, renderToStaticMarkup } = require('react-dom/server')
-const Loadable = require('react-loadable')
-const { getBundles } = require('react-loadable/webpack')
-
-const getWebpackApp = require('./webpackNodeApp')
-const client = require('./client')
-const getCSS = require('./getCSS')
-
-const createHTML = ({
- body,
- css = '',
- initialProps,
- scripts = []
-}) => ([
- ' ',
- css,
- body,
- propsScript(initialProps),
- scripts.map(src => ``).join('')
-].join(''))
-
-const propsScript = initialProps =>
- initialProps ? `` : ''
-
-const render = (Component, props, isStatic, modules = []) =>
- (isStatic ? renderToStaticMarkup : renderToString)(
- React.createElement(Loadable.Capture, {
- report: mod => modules.push(mod)
- },
- React.createElement(Component, props)
- )
- )
-
-const renderHTML = async (Component, options) => {
- const modules = []
- const isStatic = options.static || !options.outDir
- const base = options.basename || ''
- const script = base + '/bundle.js'
-
- const hasInitialProps = typeof Component.getInitialProps === 'function'
- const getProps = hasInitialProps ? Component.getInitialProps : () => ({})
-
- const initialProps = await getProps(Object.assign({ Component }, options))
- const props = Object.assign({}, options, initialProps)
-
- const preloaded = await Loadable.preloadAll()
- const body = render(Component, props, isStatic, modules)
-
- const loadableStats = isStatic ? {} : require(path.join(__dirname, './TMP/react-loadable.json'))
- const bundles = isStatic ? [] : getBundles(loadableStats, modules)
- .map(bundle => bundle.file)
- .map(src => base + '/' + src)
-
- const scripts = isStatic ? [] : [
- script,
- ...bundles
- ]
-
- let css = ''
- if (props.cssLibrary) {
- css = getCSS(Component, props)
- }
-
- const html = createHTML({
- body,
- css,
- initialProps: isStatic ? null : props,
- scripts
- })
-
- return html
-}
-
-const writePage = async (Component, options) => {
- const html = await renderHTML(Component, options)
-
- if (options.outDir) {
- const dir = path.join(
- process.cwd(),
- options.outDir,
- options.pathname || ''
- )
- if (!fs.existsSync(dir)) {
- fs.mkdirSync(dir)
- }
- const name = path.join(dir, 'index.html')
- fs.writeFileSync(name, html)
- }
-
- return html
-}
-
-const createStatic = async (filename, baseOptions) => {
- const Component = await getWebpackApp(filename, baseOptions)
-
- const options = Object.assign({}, Component.defaultProps, baseOptions)
-
- const bundle = await client(filename, options)
-
- let html
- if (options.routes && options.routes.length) {
- html = await options.routes.map(async pathname => {
- const res = await writePage(Component, Object.assign({}, options, {
- pathname
- }))
- return res
- })
- } else {
- html = await writePage(Component, options)
- }
-
-
- return { html, bundle }
-}
-
-module.exports = createStatic
diff --git a/lib/static/webpackNodeApp.js b/lib/static/webpackNodeApp.js
deleted file mode 100644
index 793c590..0000000
--- a/lib/static/webpackNodeApp.js
+++ /dev/null
@@ -1,82 +0,0 @@
-const path = require('path')
-const webpack = require('webpack')
-const merge = require('webpack-merge')
-
-// bundle App for usage in node when a custom webpack config is provided
-const config = {
- mode: 'development',
- output: {
- path: path.join(__dirname, './TMP'),
- filename: 'App.js',
- libraryExport: 'default',
- libraryTarget: 'umd',
- },
- target: 'node',
- resolve: {
- modules: []
- },
- module: {
- rules: [
- {
- test: /\.js$/,
- exclude: /node_modules/,
- use: {
- loader: require.resolve('babel-loader'),
- options: {
- presets: [
- 'babel-preset-env',
- 'babel-preset-stage-0',
- 'babel-preset-react'
- ].map(require.resolve),
- plugins: [
- require.resolve('babel-plugin-transform-runtime')
- ]
- }
- }
- },
- ]
- },
- plugins: [],
-}
-
-const bundleApp = (filename, options = {}) => {
- const dirname = path.dirname(filename)
-
- config.entry = filename
-
- config.resolve.modules.unshift(
- dirname,
- path.join(process.cwd(), 'node_modules'),
- path.join(dirname, 'node_modules'),
- )
-
- const userConfig = require(options.config)
- const mergedConfig = merge(config, userConfig)
- const compiler = webpack(mergedConfig)
-
- return new Promise((resolve, reject) => {
- compiler.run((err, stats) => {
- if (err) {
- reject(err)
- return
- }
-
- try {
- const App = require('./TMP/App')
- resolve(App)
- } catch (e) {
- reject(e)
- }
- })
- })
-}
-
-const getApp = async (filename, options) => {
- if (!options.config) {
- const req = require(filename)
- return req.default || req
- }
- return await bundleApp(filename, options)
-}
-
-module.exports = getApp
diff --git a/lib/template.js b/lib/template.js
new file mode 100644
index 0000000..555f2fb
--- /dev/null
+++ b/lib/template.js
@@ -0,0 +1,25 @@
+module.exports = ({
+ html = '',
+ css = '',
+ scripts,
+ js,
+ publicPath,
+ title = 'x0',
+ meta = [],
+ links = [],
+ static: staticBuild
+}) =>
+`
+
+
+
+
+${title}
+${meta.map(({ name, content }) => ` `).join('\n')}
+${links.map(({ rel, href }) => ` `).join('\n')}
+
+${css}
+
+${html}
+${staticBuild ? '' : scripts}
+`
diff --git a/package.json b/package.json
index 1f694ec..3fcedcb 100644
--- a/package.json
+++ b/package.json
@@ -1,15 +1,14 @@
{
"name": "@compositor/x0",
- "version": "4.0.3",
+ "version": "5.0.0-14",
"description": "Zero-config React development environment and static site generator",
"main": "index.js",
"bin": {
- "x0": "bin/cli.js"
+ "x0": "cli.js"
},
"scripts": {
- "start": "./bin/cli.js docs/App.js -op 8888",
- "static": "./bin/cli.js build docs/App.js --static -d docs",
- "build": "./bin/cli.js build docs/App.js -d docs -c docs/webpack.config.js",
+ "start": "./cli.js docs -op 8888",
+ "build": "./cli.js build docs",
"test": "nyc ava",
"cover": "nyc report --reporter=html --reporter=lcov"
},
@@ -17,49 +16,59 @@
"author": "Brent Jackson",
"license": "MIT",
"dependencies": {
- "babel-core": "^6.26.0",
- "babel-loader": "^7.1.2",
- "babel-minify-webpack-plugin": "^0.2.0",
- "babel-plugin-dynamic-import-node": "^1.2.0",
- "babel-plugin-syntax-dynamic-import": "^6.18.0",
+ "@compositor/jsx-loader": "^1.0.0-3",
+ "@compositor/log": "^1.0.0-0",
+ "@mdx-js/loader": "^0.9.0",
+ "@mdx-js/mdx": "^0.9.0",
+ "babel-core": "^6.26.3",
+ "babel-loader": "^7.1.4",
+ "babel-plugin-macros": "^2.2.1",
"babel-plugin-transform-runtime": "^6.23.0",
- "babel-preset-env": "^1.6.0",
+ "babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"babel-register": "^6.26.0",
- "chalk": "^2.1.0",
- "fela": "^6.1.0",
- "fela-dom": "^7.0.0",
+ "chalk": "^2.4.1",
+ "clipboardy": "^1.2.3",
+ "connect-history-api-fallback": "^1.5.0",
+ "emotion": "^9.1.3",
+ "emotion-server": "^9.1.3",
+ "find-up": "^2.1.0",
+ "fs-extra": "^6.0.1",
"glamor": "^2.20.40",
- "glamorous": "^4.11.0",
- "meow": "^3.7.0",
- "ora": "^1.3.0",
- "pkg-up": "^2.0.0",
- "react": "^16.2.0",
- "react-dev-utils": "^4.2.1",
- "react-dom": "^16.2.0",
- "react-fela": "^6.1.1",
- "react-loadable": "^5.3.1",
- "read-pkg-up": "^2.0.0",
- "update-notifier": "^2.2.0",
- "webpack": "^4.0.0",
- "webpack-dev-server": "^3.0.0",
- "webpack-merge": "^4.1.1"
+ "html-minifier": "^3.5.15",
+ "koa-connect": "^2.0.1",
+ "meow": "^5.0.0",
+ "mini-html-webpack-plugin": "^0.2.3",
+ "pkg-conf": "^2.1.0",
+ "react": "^16.4.0",
+ "react-dev-utils": "^5.0.1",
+ "react-dom": "^16.4.0",
+ "react-router": "^4.2.0",
+ "react-router-dom": "^4.2.2",
+ "read-pkg-up": "^3.0.0",
+ "semver": "^5.5.0",
+ "update-notifier": "^2.5.0",
+ "webpack": "^4.8.3",
+ "webpack-merge": "^4.1.2",
+ "webpack-serve": "^1.0.2"
},
"devDependencies": {
- "ava": "^0.24.0",
+ "@compositor/logo": "^1.3.5",
+ "@compositor/md-loader": "^1.0.27",
+ "ava": "^0.25.0",
"isomorphic-fetch": "^2.2.1",
- "nano-style": "^1.0.0-20",
- "nyc": "^11.2.1",
+ "nano-style": "^1.0.0",
+ "nyc": "^11.8.0",
"raw-loader": "^0.5.1",
- "react-router-dom": "^4.2.2",
- "refunk": "^2.0.0-1",
- "styled-components": "^2.2.4",
- "styled-system": "^1.0.7"
+ "rebass": "^2.0.0-2",
+ "refunk": "^3.0.1",
+ "rimraf": "^2.6.2",
+ "styled-components": "^3.3.0",
+ "styled-system": "^2.2.5"
},
"x0": {
"title": "Compositor x0",
- "cssLibrary": "styled-components",
"_basename": "/x0"
},
"ava": {
diff --git a/test/build.js b/test/build.js
new file mode 100644
index 0000000..1568e2e
--- /dev/null
+++ b/test/build.js
@@ -0,0 +1,45 @@
+import fs from 'fs'
+import path from 'path'
+import test from 'ava'
+import rimraf from 'rimraf'
+import build from '../lib/build'
+
+const input = path.resolve('test/components')
+const output = path.resolve('test/output')
+const htmlFile = path.resolve('test/output/index.html')
+const propsFile = path.resolve('test/output/props/index.html')
+const bundleFile = path.resolve('test/output/bundle.js')
+
+const options = {
+ input,
+ dirname: input,
+ outDir: output,
+}
+
+const clean = () => {
+ if (fs.existsSync(output)) {
+ rimraf.sync(output)
+ }
+ // fs.remove(output)
+}
+
+test.before(clean)
+test.after(clean)
+
+test('static renders', async t => {
+ const res = await build(options)
+ const html = fs.readFileSync(htmlFile, 'utf8')
+ t.snapshot(html)
+})
+
+test('static uses getInitialProps method', async t => {
+ const res = await build(options)
+ const html = fs.readFileSync(propsFile, 'utf8')
+ t.snapshot(html)
+})
+
+test.skip('static makes a directory', async t => {
+ clean()
+ const res = await build(options)
+ t.pass()
+})
diff --git a/test/components/Hello.js b/test/components/index.js
similarity index 100%
rename from test/components/Hello.js
rename to test/components/index.js
diff --git a/test/components/Props.js b/test/components/props.js
similarity index 100%
rename from test/components/Props.js
rename to test/components/props.js
diff --git a/test/snapshots/build.js.md b/test/snapshots/build.js.md
new file mode 100644
index 0000000..5bc7e2b
--- /dev/null
+++ b/test/snapshots/build.js.md
@@ -0,0 +1,17 @@
+# Snapshot report for `test/build.js`
+
+The actual snapshot is saved in `build.js.snap`.
+
+Generated by [AVA](https://ava.li).
+
+## static renders
+
+> Snapshot 1
+
+ 'x0
Hello '
+
+## static uses getInitialProps method
+
+> Snapshot 1
+
+ 'x0
Hello '
diff --git a/test/snapshots/build.js.snap b/test/snapshots/build.js.snap
new file mode 100644
index 0000000..ff845dd
Binary files /dev/null and b/test/snapshots/build.js.snap differ
diff --git a/test/snapshots/static.js.md b/test/snapshots/static.js.md
index 1a9b81a..3267fcd 100644
--- a/test/snapshots/static.js.md
+++ b/test/snapshots/static.js.md
@@ -4,28 +4,14 @@ The actual snapshot is saved in `static.js.snap`.
Generated by [AVA](https://ava.li).
-## static picks up routes config
-
-> Snapshot 1
-
- [
- Promise {},
- ]
-
## static renders
> Snapshot 1
- 'Hello '
+ 'x0
Hello '
## static uses getInitialProps method
> Snapshot 1
- 'Hello '
-
-## static writes
-
-> Snapshot 1
-
- 'Hello '
+ 'x0
Hello '
diff --git a/test/snapshots/static.js.snap b/test/snapshots/static.js.snap
index 2b58ca5..6679696 100644
Binary files a/test/snapshots/static.js.snap and b/test/snapshots/static.js.snap differ
diff --git a/test/snapshots/template.js.md b/test/snapshots/template.js.md
new file mode 100644
index 0000000..acefd59
--- /dev/null
+++ b/test/snapshots/template.js.md
@@ -0,0 +1,43 @@
+# Snapshot report for `test/template.js`
+
+The actual snapshot is saved in `template.js.snap`.
+
+Generated by [AVA](https://ava.li).
+
+## template snapshot
+
+> Snapshot 1
+
+ `␊
+ ␊
+ ␊
+ ␊
+ ␊
+ Test ␊
+ ␊
+ ␊
+ ␊
+ ␊
+ ␊
+ hello
␊
+ ␊
+ `
+
+## template static snapshot
+
+> Snapshot 1
+
+ `␊
+ ␊
+ ␊
+ ␊
+ ␊
+ Test ␊
+ ␊
+ ␊
+ ␊
+ ␊
+ ␊
+ hello
␊
+ ␊
+ `
diff --git a/test/snapshots/template.js.snap b/test/snapshots/template.js.snap
new file mode 100644
index 0000000..1071cca
Binary files /dev/null and b/test/snapshots/template.js.snap differ
diff --git a/test/static.js b/test/static.js
deleted file mode 100644
index a424785..0000000
--- a/test/static.js
+++ /dev/null
@@ -1,77 +0,0 @@
-import fs from 'fs'
-import path from 'path'
-import test from 'ava'
-import x0Static from '../lib/static'
-
-const hello = path.join(__dirname, './components/Hello.js')
-const withprops = path.join(__dirname, './components/Props.js')
-const output = path.join(__dirname, './output')
-const htmlFile = path.join(__dirname, './output/index.html')
-const bundleFile = path.join(__dirname, './output/bundle.js')
-
-const clean = () => {
- if (fs.existsSync(htmlFile)) {
- fs.unlinkSync(htmlFile)
- }
- if (fs.existsSync(bundleFile)) {
- fs.unlinkSync(bundleFile)
- }
-}
-
-test.before(clean)
-test.after(clean)
-
-test.cb('static renders', t => {
- x0Static(hello, {})
- .then(result => {
- t.is(typeof result, 'object')
- t.snapshot(result.html)
- t.end()
- })
-})
-
-test.cb('static writes', t => {
- x0Static(hello, {
- outDir: 'test/output'
- })
- .then(result => {
- t.is(typeof result, 'object')
- t.snapshot(result.html)
- t.end()
- })
-})
-
-test.cb('static uses getInitialProps method', t => {
- x0Static(withprops, {})
- .then(result => {
- t.is(typeof result, 'object')
- t.snapshot(result.html)
- t.end()
- })
-})
-
-test.cb('static picks up routes config', t => {
- x0Static(hello, {
- routes: [
- '/'
- ]
- })
- .then(result => {
- t.is(typeof result, 'object')
- t.snapshot(result.html)
- t.end()
- })
-})
-
-test.cb('static makes a directory', t => {
- if (fs.existsSync(output)) {
- fs.rmdirSync(output)
- }
-
- x0Static(hello, {
- outDir: 'test/output'
- })
- .then(result => {
- t.end()
- })
-})
diff --git a/test/template.js b/test/template.js
new file mode 100644
index 0000000..25f5b00
--- /dev/null
+++ b/test/template.js
@@ -0,0 +1,35 @@
+import test from 'ava'
+import template from '../lib/template'
+
+test('template snapshot', t => {
+ const html = template({
+ title: 'Test',
+ html: 'hello',
+ css: '',
+ scripts: '',
+ meta: [
+ { name: 'twitter:card', content: 'summary' }
+ ],
+ links: [
+ { rel: 'stylesheet', content: 'hello.css' }
+ ]
+ })
+ t.snapshot(html)
+})
+
+test('template static snapshot', t => {
+ const html = template({
+ title: 'Test',
+ html: 'hello',
+ css: '',
+ static: true,
+ scripts: '',
+ meta: [
+ { name: 'twitter:card', content: 'summary' }
+ ],
+ links: [
+ { rel: 'stylesheet', content: 'hello.css' }
+ ]
+ })
+ t.snapshot(html)
+})