├── src
├── containers
│ ├── Page
│ │ ├── Page.scss
│ │ └── Page.js
│ ├── Post
│ │ ├── Post.scss
│ │ └── Post.js
│ ├── Homepage
│ │ ├── Homepage.scss
│ │ └── Homepage.js
│ ├── Root
│ │ ├── Root.js
│ │ ├── Root.prod.js
│ │ └── Root.dev.js
│ ├── PostsIndex
│ │ ├── PostsIndex.scss
│ │ ├── query.js
│ │ └── PostsIndex.js
│ ├── PrimaryMenu
│ │ ├── PrimaryMenu.scss
│ │ └── PrimaryMenu.js
│ ├── App
│ │ ├── App.scss
│ │ └── App.js
│ └── index.js
├── components
│ ├── Error
│ │ ├── Error.scss
│ │ └── Error.js
│ ├── NotFound
│ │ ├── NotFound.scss
│ │ └── NotFound.js
│ ├── Loading
│ │ └── Loading.js
│ ├── MenuItem
│ │ ├── MenuItem.scss
│ │ └── MenuItem.js
│ ├── Header
│ │ ├── Header.scss
│ │ └── Header.js
│ ├── PostListing
│ │ ├── PostListing.scss
│ │ └── PostListing.js
│ ├── DevTools
│ │ └── DevTools.js
│ └── index.js
├── theme
│ ├── style.scss
│ └── global.scss
├── assets
│ └── tophat.png
├── store
│ ├── index.js
│ ├── sagas.js
│ ├── reducer.js
│ ├── wordpress.js
│ └── create.js
├── helpers
│ ├── index.js
│ ├── runSagas.js
│ ├── Html.js
│ └── htmlParser.js
├── routes
│ ├── routes.js
│ └── index.js
├── client.js
├── config.js
└── server.js
├── .gitignore
├── static
└── favicon.ico
├── .editorconfig
├── .babelrc
├── webpack
├── webpack-dev-server.js
├── webpack-isomorphic-tools.js
├── prod.config.js
└── dev.config.js
├── LICENSE
├── bin
└── www
├── README.md
└── package.json
/src/containers/Page/Page.scss:
--------------------------------------------------------------------------------
1 | .Page {}
2 |
--------------------------------------------------------------------------------
/src/containers/Post/Post.scss:
--------------------------------------------------------------------------------
1 | .Post {}
2 |
--------------------------------------------------------------------------------
/src/components/Error/Error.scss:
--------------------------------------------------------------------------------
1 | .Error {}
2 |
--------------------------------------------------------------------------------
/src/containers/Homepage/Homepage.scss:
--------------------------------------------------------------------------------
1 | .Homepage {}
--------------------------------------------------------------------------------
/src/components/NotFound/NotFound.scss:
--------------------------------------------------------------------------------
1 | .NotFound {}
2 |
--------------------------------------------------------------------------------
/src/theme/style.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * # Application Styles
3 | */
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .DS_Store
3 | npm-debug.log
4 |
5 | node_modules/
6 | static/dist/
7 |
--------------------------------------------------------------------------------
/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outlandishideas/kasia-boilerplate/master/static/favicon.ico
--------------------------------------------------------------------------------
/src/assets/tophat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outlandishideas/kasia-boilerplate/master/src/assets/tophat.png
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | export { default as createStore } from './create'
2 | export { WP } from './wordpress'
3 |
--------------------------------------------------------------------------------
/src/helpers/index.js:
--------------------------------------------------------------------------------
1 | export { default as Html } from './Html'
2 | export { default as runSagas } from './runSagas'
3 |
--------------------------------------------------------------------------------
/src/containers/Root/Root.js:
--------------------------------------------------------------------------------
1 | module.exports = process.env.NODE_ENV === 'production'
2 | ? require('./Root.prod')
3 | : require('./Root.dev')
4 |
--------------------------------------------------------------------------------
/src/components/Loading/Loading.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | export default function Loading () {
4 | return (
5 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/containers/PostsIndex/PostsIndex.scss:
--------------------------------------------------------------------------------
1 | .PostsIndex {
2 | width: 100%;
3 | float: left;
4 | }
5 |
6 | .PostListings {
7 | float: left;
8 | width: 100%;
9 | list-style-type: none;
10 | padding: 0;
11 | }
12 |
--------------------------------------------------------------------------------
/src/containers/PrimaryMenu/PrimaryMenu.scss:
--------------------------------------------------------------------------------
1 | .PrimaryMenu {
2 | width: 100%;
3 | float: left;
4 | list-style-type: none;
5 | margin: 0;
6 | padding: 0;
7 | background-color: lightblue;
8 | border-bottom: 1px solid darkblue;
9 | }
10 |
--------------------------------------------------------------------------------
/src/store/sagas.js:
--------------------------------------------------------------------------------
1 | /**
2 | * # Sagas
3 | */
4 |
5 | import { wpSagas } from './wordpress'
6 |
7 | export default function * () {
8 | yield [
9 | ...wpSagas
10 | // your sagas go here, e.g. fork(someSagaGenerator)
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/src/containers/App/App.scss:
--------------------------------------------------------------------------------
1 | .App {
2 | width: 100%;
3 | max-width: 1024px;
4 | padding: 25px;
5 | }
6 |
7 | .content {
8 | width: 100%;
9 | float: left;
10 | clear: both;
11 | padding: 15px;
12 | }
13 |
14 | @media screen and (min-width: 1024px) {
15 | .App {
16 | margin: 0 auto;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/MenuItem/MenuItem.scss:
--------------------------------------------------------------------------------
1 | .ActiveMenuItem {
2 | text-decoration: underline;
3 | }
4 |
5 | .MenuItem {
6 | float: left;
7 | padding: 15px;
8 | padding-right: 0;
9 |
10 | a {
11 | text-decoration: none;
12 | color: black;
13 | }
14 |
15 | a:hover {
16 | @extend .ActiveMenuItem
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/Header/Header.scss:
--------------------------------------------------------------------------------
1 | .Header {
2 | float: left;
3 | clear: both;
4 | width: 100%;
5 | height: 50px;
6 | margin: 15px;
7 | margin-bottom: 30px;
8 |
9 | img,
10 | h1 {
11 | display: block;
12 | float: left;
13 | }
14 |
15 | h1 {
16 | margin-top: 5px;
17 | margin-left: 15px;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/store/reducer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * # Reducer
3 | */
4 |
5 | import { routerReducer } from 'react-router-redux'
6 | import { combineReducers } from 'redux'
7 |
8 | import { wpReducer } from './wordpress'
9 |
10 | export default combineReducers({
11 | routing: routerReducer,
12 | ...wpReducer
13 | // your reducers go here, e.g. user: userReducer
14 | })
15 |
--------------------------------------------------------------------------------
/src/theme/global.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * # Global Styles
3 | */
4 |
5 | @import "./style";
6 |
7 | * {
8 | box-sizing: border-box;
9 | font-family: sans-serif;
10 | }
11 |
12 | html,
13 | body {
14 | width: 100%;
15 | height: 100%;
16 | min-width: 720px;
17 | background-color: white;
18 | padding: 0;
19 | margin: 0;
20 | }
21 |
22 | body #container {
23 | width: 100%;
24 | height: 100%;
25 | }
26 |
--------------------------------------------------------------------------------
/src/helpers/runSagas.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Run all `sagas` until they are complete.
3 | * @param {Object} store Enhanced redux store with `runSaga` method
4 | * @param {Array} sagas Array of saga operations
5 | * @returns {Promise}
6 | */
7 | export default function runSagas (store, sagas) {
8 | return sagas.reduce((promise, saga) => {
9 | return promise.then(() => store.runSaga(saga).done)
10 | }, Promise.resolve())
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/Header/Header.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import config from '../../config'
4 |
5 | import tophatPng from '../../assets/tophat.png'
6 | import styles from './Header.scss'
7 |
8 | export default function Header () {
9 | return (
10 |
11 |
12 | {config.app.title}
13 |
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/src/components/PostListing/PostListing.scss:
--------------------------------------------------------------------------------
1 | .PostListing {
2 | width: 32%;
3 | margin-right: 1%;
4 | margin-bottom: 20px;
5 | padding: 10px;
6 | float: left;
7 | border: 1px solid grey;
8 | }
9 |
10 | .PostListing__title {
11 | width: 100%;
12 | white-space: nowrap;
13 | text-overflow: ellipsis;
14 | font-weight: bold;
15 | overflow: hidden;
16 | }
17 |
18 | .PostListing__excerpt {
19 | font-size: 0.75rem;
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/DevTools/DevTools.js:
--------------------------------------------------------------------------------
1 | /**
2 | * # DevTools Container
3 | */
4 |
5 | import React from 'react'
6 | import { createDevTools } from 'redux-devtools'
7 | import LogMonitor from 'redux-devtools-log-monitor'
8 | import DockMonitor from 'redux-devtools-dock-monitor'
9 |
10 | export default createDevTools(
11 |
12 |
13 |
14 | )
15 |
--------------------------------------------------------------------------------
/src/containers/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * # Containers
3 | */
4 |
5 | export { default as Root } from './Root/Root'
6 | export { default as App } from './App/App'
7 | export { default as Page } from './Page/Page'
8 | export { default as Homepage } from './Homepage/Homepage'
9 | export { default as Post } from './Post/Post'
10 | export { default as PrimaryMenu } from './PrimaryMenu/PrimaryMenu'
11 | export { default as PostsIndex } from './PostsIndex/PostsIndex'
12 |
--------------------------------------------------------------------------------
/src/components/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * # Components
3 | */
4 |
5 | export { default as Error } from './Error/Error'
6 | export { default as DevTools } from './DevTools/DevTools'
7 | export { default as NotFound } from './NotFound/NotFound'
8 | export { default as Header } from './Header/Header'
9 | export { default as MenuItem } from './MenuItem/MenuItem'
10 | export { default as PostListing } from './PostListing/PostListing'
11 | export { default as Loading } from './Loading/Loading'
12 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 |
9 | # Change these settings to your own preference
10 | indent_style = space
11 | indent_size = 2
12 |
13 | # We recommend you to keep these unchanged
14 | end_of_line = lf
15 | charset = utf-8
16 | trim_trailing_whitespace = true
17 | insert_final_newline = false
18 |
19 | [*.js]
20 |
21 | insert_final_newline = true
22 |
--------------------------------------------------------------------------------
/src/components/MenuItem/MenuItem.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { Link } from 'react-router'
3 |
4 | import classNames from 'classnames'
5 |
6 | import styles from './MenuItem.scss'
7 |
8 | export default function MenuItem ({ item, to, active }) {
9 | const classes = classNames(styles.MenuItem, {
10 | [styles.ActiveMenuItem]: active
11 | })
12 |
13 | return (
14 |
15 | {item.title}
16 |
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/src/routes/routes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * # App Routes
3 | */
4 |
5 | import React from 'react'
6 | import { Route } from 'react-router'
7 |
8 | import { Page, Post, PostsIndex } from '../containers'
9 |
10 | const PageBySlug = Page((props) => props.params.slug)
11 |
12 | export default [
13 | ,
14 | ,
15 | ,
16 |
17 | ]
18 |
--------------------------------------------------------------------------------
/src/routes/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * # Router
3 | */
4 |
5 | import React, { Component } from 'react'
6 | import { IndexRoute, Route, Redirect } from 'react-router'
7 |
8 | import { App, Page } from '../containers'
9 | import { NotFound } from '../components'
10 | import AppRoutes from './routes'
11 |
12 | export default (
13 |
14 |
15 |
16 | {AppRoutes}
17 |
18 |
19 | )
20 |
--------------------------------------------------------------------------------
/src/components/PostListing/PostListing.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { Link } from 'react-router'
3 |
4 | import parse from '../../helpers/htmlParser'
5 |
6 | import styles from './PostListing.scss'
7 |
8 | export default function PostListing (props) {
9 | const { title, excerpt, slug } = props
10 |
11 | return (
12 |
13 | {title}
14 | {parse(excerpt)}
15 | Read More
16 |
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/NotFound/NotFound.js:
--------------------------------------------------------------------------------
1 | /**
2 | * # Not Found Container
3 | */
4 |
5 | import React from 'react'
6 | import { Link } from 'react-router'
7 | import Helmet from 'react-helmet'
8 |
9 | import styles from './NotFound.scss'
10 |
11 | export default function NotFound () {
12 | return (
13 |
14 |
15 |
Not Found
16 |
The page you have requested was not found.
17 |
18 | Go to the Homepage.
19 |
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "react",
4 | "es2015",
5 | "stage-0"
6 | ],
7 | "plugins": [
8 | "transform-runtime",
9 | "add-module-exports",
10 | "transform-decorators-legacy",
11 | "transform-react-display-name"
12 | ],
13 | "env": {
14 | "development": {
15 | "plugins": [
16 | "typecheck",
17 | ["react-transform", {
18 | "transforms": [{
19 | "transform": "react-transform-catch-errors",
20 | "imports": ["react", "redbox-react"]
21 | }]
22 | }]
23 | ]
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/store/wordpress.js:
--------------------------------------------------------------------------------
1 | /**
2 | * # Kasia Setup
3 | */
4 |
5 | import { default as _WP } from 'wpapi'
6 | import Kasia from 'kasia'
7 | import KasiaWpMenusPlugin from 'kasia-plugin-wp-api-menus'
8 |
9 | import config from '../config'
10 |
11 | export const WP = new _WP({ endpoint: config.wpapi })
12 |
13 | const { kasiaSagas, kasiaReducer } = Kasia({
14 | WP,
15 | plugins: [KasiaWpMenusPlugin],
16 | contentTypes: [{
17 | name: 'Reports',
18 | plural: 'reports',
19 | slug: 'reports'
20 | }]
21 | })
22 |
23 | export {
24 | kasiaSagas as wpSagas,
25 | kasiaReducer as wpReducer
26 | }
27 |
--------------------------------------------------------------------------------
/src/client.js:
--------------------------------------------------------------------------------
1 | /**
2 | * # Client
3 | */
4 |
5 | import 'babel-polyfill'
6 |
7 | import React from 'react'
8 | import ReactDOM from 'react-dom'
9 | import { browserHistory } from 'react-router'
10 | import { syncHistoryWithStore } from 'react-router-redux'
11 |
12 | import { Root } from './containers'
13 | import routes from './routes'
14 | import createStore from './store/create'
15 |
16 | const store = createStore(browserHistory, window.__STATE__ || {})
17 | const history = syncHistoryWithStore(browserHistory, store)
18 |
19 | ReactDOM.render(
20 | ,
21 | document.getElementById('container')
22 | )
23 |
--------------------------------------------------------------------------------
/src/components/Error/Error.js:
--------------------------------------------------------------------------------
1 | /**
2 | * # Not Found Container
3 | */
4 |
5 | import React, { PropTypes } from 'react'
6 | import Helmet from 'react-helmet'
7 |
8 | import styles from './Error.scss'
9 |
10 | export default function Error ({
11 | code = 500,
12 | message = 'Something went wrong trying to serve your request.'
13 | }) {
14 | return (
15 |
23 | )
24 | }
25 |
26 | Error.propTypes = {
27 | code: PropTypes.number,
28 | message: PropTypes.string
29 | }
30 |
--------------------------------------------------------------------------------
/src/containers/Post/Post.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { connectWpPost } from 'kasia/connect'
3 | import ContentTypes from 'kasia/types'
4 |
5 | import { Loading } from '../../components'
6 | import parse from '../../helpers/htmlParser'
7 |
8 | import styles from './Post.scss'
9 |
10 | @connectWpPost(ContentTypes.Post, (props) => props.params.slug)
11 | export default class Post extends Component {
12 | render () {
13 | const { post } = this.props.kasia
14 |
15 | if (!post) {
16 | return
17 | }
18 |
19 | return (
20 |
21 |
{post.title}
22 | {parse(post.content)}
23 |
24 | )
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/containers/PostsIndex/query.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Get the page with slug 'posts' and all Post content items.
3 | * @param {Object} wpapi WP-API interface
4 | * @param {Object} props Component's props
5 | * @returns {Promise}
6 | */
7 | export default function PostsListingQuery (wpapi, props) {
8 | let page = typeof props.params.page !== 'undefined'
9 | ? Number(props.params.page)
10 | : false
11 |
12 | page = page >= 2 ? page : false
13 |
14 | const postsQuery = page
15 | ? wpapi.posts().page(page)
16 | : wpapi.posts()
17 |
18 | const promises = [
19 | wpapi.pages().slug('posts').get(),
20 | postsQuery.perPage(5).get()
21 | ]
22 |
23 | return Promise
24 | .all(promises)
25 | .then((results) => [].concat(...results))
26 | }
27 |
--------------------------------------------------------------------------------
/src/containers/App/App.js:
--------------------------------------------------------------------------------
1 | /**
2 | * # App Container
3 | */
4 |
5 | import React, { Component } from 'react'
6 | import Helmet from 'react-helmet'
7 |
8 | import { Header } from '../../components'
9 | import { PrimaryMenu } from '../index'
10 | import config from '../../config'
11 |
12 | import styles from './App.scss'
13 |
14 | export default class App extends Component {
15 | render () {
16 | const { pathname } = this.props.location
17 |
18 | return (
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | {this.props.children}
28 |
29 |
30 | )
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/containers/Root/Root.prod.js:
--------------------------------------------------------------------------------
1 | /**
2 | * # Root (Production)
3 | */
4 |
5 | import React, { Component, PropTypes } from 'react'
6 | import { Provider } from 'react-redux'
7 | import { Router, RouterContext } from 'react-router'
8 |
9 | export default class Root extends Component {
10 | static propTypes = {
11 | store: PropTypes.object.isRequired,
12 | history: PropTypes.object.isRequired,
13 | routes: PropTypes.node.isRequired
14 | }
15 |
16 | render() {
17 | const { store, history, routes, type, renderProps } = this.props
18 |
19 | return (
20 |
21 | {type === 'server'
22 | ?
23 | : }
24 |
25 | )
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/containers/Root/Root.dev.js:
--------------------------------------------------------------------------------
1 | /**
2 | * # Root (Development)
3 | */
4 |
5 | import React, { Component, PropTypes } from 'react'
6 | import { Provider } from 'react-redux'
7 | import { Router, RouterContext } from 'react-router'
8 |
9 | import { DevTools } from '../../components'
10 |
11 | export default class Root extends Component {
12 | static propTypes = {
13 | store: PropTypes.object.isRequired,
14 | history: PropTypes.object.isRequired,
15 | routes: PropTypes.node.isRequired
16 | }
17 |
18 | render() {
19 | const { store, history, routes, type, renderProps } = this.props
20 |
21 | return (
22 |
23 | {type === 'server'
24 | ?
25 | : }
26 | {/*{type === 'client' && !window.devToolsExtension*/}
27 | {/*? */}
28 | {/*: null}*/}
29 |
30 | )
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/webpack/webpack-dev-server.js:
--------------------------------------------------------------------------------
1 | /**
2 | * # Webpack Dev Server
3 | */
4 |
5 | var Express = require('express')
6 | var webpack = require('webpack')
7 |
8 | var config = require('../src/config')
9 | var webpackConfig = require('./dev.config')
10 |
11 | var compiler = webpack(webpackConfig)
12 | var host = config.host || 'localhost'
13 | var port = (Number(config.port) + 1) || 3001
14 |
15 | var serverOptions = {
16 | contentBase: 'http://' + host + ':' + port,
17 | quiet: true,
18 | noInfo: true,
19 | hot: true,
20 | inline: true,
21 | lazy: false,
22 | publicPath: webpackConfig.output.publicPath,
23 | headers: { 'Access-Control-Allow-Origin': '*' },
24 | stats: { colors: true }
25 | }
26 |
27 | var app = new Express()
28 |
29 | app.use(require('webpack-dev-middleware')(compiler, serverOptions))
30 | app.use(require('webpack-hot-middleware')(compiler))
31 |
32 | app.listen(port, function onAppListening(err) {
33 | if (err) {
34 | console.error(err)
35 | } else {
36 | console.info('==> Webpack development server listening on port %s', port)
37 | }
38 | })
39 |
--------------------------------------------------------------------------------
/src/containers/Homepage/Homepage.js:
--------------------------------------------------------------------------------
1 |
2 | import React, { Component, PropTypes } from 'react'
3 | import { connectWpPost } from 'kasia/connect'
4 | import ContentTypes from 'kasia/types'
5 | import Helmet from 'react-helmet'
6 |
7 | import { Loading } from '../../components'
8 | import parse from '../../helpers/htmlParser'
9 |
10 | import styles from './Homepage.scss'
11 |
12 | @connectWpPost(ContentTypes.Page, 'homepage')
13 | export default class Homepage extends Component {
14 | static propTypes = {
15 | params: PropTypes.object
16 | }
17 |
18 | render () {
19 | const { page } = this.props.kasia
20 |
21 | let children =
22 |
23 | if (page) {
24 | children = (
25 |
26 |
27 |
{page.title}
28 | {parse(page.content.rendered)}
29 |
30 | )
31 | }
32 |
33 | return (
34 |
35 | {children}
36 |
37 | )
38 | }
39 |
40 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Outlandish
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/bin/www:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | /**
3 | * # WWW Script
4 | */
5 | 'use strict'
6 |
7 | var fs = require('fs')
8 | var path = require('path')
9 | var WebpackIsomorphicTools = require('webpack-isomorphic-tools')
10 |
11 | var iso = require('../webpack/webpack-isomorphic-tools')
12 |
13 | var rootDir = path.resolve(__dirname, '../')
14 | var babelrc = fs.readFileSync('.babelrc')
15 |
16 | global.__CLIENT__ = false
17 | global.__SERVER__ = true
18 | global.__DEVELOPMENT__ = process.env.NODE_ENV !== 'production'
19 |
20 | try {
21 | var config = JSON.parse(babelrc)
22 | } catch (err) {
23 | console.error('==> ERROR: Error parsing .babelrc')
24 | console.error(err)
25 | }
26 |
27 | require('babel-register')(config)
28 | require('babel-polyfill')
29 |
30 | if (__DEVELOPMENT__ && !process.env.DEBUG) {
31 | var cond = require('piping')({
32 | hook: true,
33 | ignore: /(\/\.|~$|\.json|\.scss$)/i
34 | })
35 |
36 | if (!cond) {
37 | return
38 | }
39 | }
40 |
41 | var tool = new WebpackIsomorphicTools(iso)
42 | .server(rootDir, function () {
43 | require('../src/server')
44 | })
45 |
46 | global.webpackIsomorphicTools = tool
47 |
--------------------------------------------------------------------------------
/src/containers/PrimaryMenu/PrimaryMenu.js:
--------------------------------------------------------------------------------
1 | /**
2 | * # Primary Menu Component
3 | */
4 |
5 | import React, { Component, PropTypes } from 'react'
6 | import { connect } from 'react-redux'
7 |
8 | import { MenuItem } from '../../components'
9 | import config from '../../config'
10 |
11 | import styles from './PrimaryMenu.scss'
12 |
13 | const getSlug = (url) => {
14 | return url.replace(config.wordpress, '')
15 | }
16 |
17 | const getMenu = (state) => (
18 | state.wordpress.menuLocations
19 | && state.wordpress.menuLocations.primary
20 | || []
21 | )
22 |
23 | @connect((state) => ({ menu: getMenu(state) }))
24 | export default class PrimaryMenu extends Component {
25 | static propTypes = {
26 | activePathName: PropTypes.string.isRequired
27 | }
28 |
29 | render () {
30 | const { menu, activePathName } = this.props
31 |
32 | return (
33 |
34 | {menu.map((item) => {
35 | const slug = getSlug(item.url)
36 | const active = slug === activePathName
37 | return
38 | })}
39 |
40 | )
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/containers/PostsIndex/PostsIndex.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { connectWpQuery } from 'kasia/connect'
3 | import Helmet from 'react-helmet'
4 |
5 | import { Loading, PostListing } from '../../components'
6 | import parse from '../../helpers/htmlParser'
7 | import query from './query'
8 |
9 | import styles from './PostsIndex.scss'
10 |
11 | @connectWpQuery(query)
12 | export default class PostsIndex extends Component {
13 | render () {
14 | const { entities: { pages, posts } } = this.props.kasia
15 |
16 | if (!pages || !posts) {
17 | return
18 | }
19 |
20 | const page = Object.values(pages)[0]
21 |
22 | return (
23 |
24 |
25 |
26 |
27 | {page.title}
28 |
29 |
30 |
31 | {parse(page.content)}
32 |
33 |
34 |
35 | {Object.values(posts).map((post) =>
36 | )}
37 |
38 |
39 | )
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * # App Config
3 | */
4 |
5 | module.exports = {
6 | host: process.env.HOST || 'localhost',
7 | wordpress: process.env.WORDPRESS || 'http://story-tiles.dev',
8 | wpapi: process.env.WORDPRESS_API || 'http://story-tiles.dev/wp-json',
9 | port: process.env.PORT || 3000,
10 | app: {
11 | title: 'Kasia Boilerplate',
12 | description: 'A universal application boilerplate with Kasia',
13 | head: {
14 | // react-helmet configuration
15 | titleTemplate: '%s | Kasia Boilerplate',
16 | defaultTitle: 'Kasia Boilerplate',
17 | meta: [
18 | { name: 'description', content: 'A universal application boilerplate with Kasia' },
19 | { charset: 'utf-8' },
20 | { property: 'og:site_name', content: '' },
21 | { property: 'og:image', content: '' },
22 | { property: 'og:locale', content: '' },
23 | { property: 'og:title', content: '' },
24 | { property: 'og:description', content: '' },
25 | { property: 'og:card', content: '' },
26 | { property: 'og:site', content: '' },
27 | { property: 'og:creator', content: '' },
28 | { property: 'og:image:width', content: '' },
29 | { property: 'og:image:height', content: '' }
30 | ]
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/containers/Page/Page.js:
--------------------------------------------------------------------------------
1 | /**
2 | * # Page Container
3 | */
4 |
5 | import React, { Component, PropTypes } from 'react'
6 | import { connectWpPost } from 'kasia/connect'
7 | import { push } from 'react-router-redux'
8 | import ContentTypes from 'kasia/types'
9 | import Helmet from 'react-helmet'
10 |
11 | import { Loading } from '../../components'
12 | import parse from '../../helpers/htmlParser'
13 |
14 | import styles from './Page.scss'
15 |
16 | class Page extends Component {
17 | static propTypes = {
18 | params: PropTypes.object
19 | }
20 |
21 | render () {
22 | const { page } = this.props.kasia
23 |
24 | let children =
25 |
26 | if (page) {
27 | children = (
28 |
29 |
30 |
{page.title}
31 | {parse(page.content.rendered)}
32 |
33 | )
34 | }
35 |
36 | return (
37 |
38 | {children}
39 |
40 | )
41 | }
42 |
43 | componentWillMount () {
44 | this.redirectWithoutPage(this.props)
45 | }
46 |
47 | componentWillReceiveProps (nextProps) {
48 | this.redirectWithoutPage(nextProps)
49 | }
50 |
51 | redirectWithoutPage (props) {
52 | const { query, page } = props.kasia
53 |
54 | if (query.complete && !page) {
55 | this.props.dispatch(push('/not-found'))
56 | }
57 | }
58 | }
59 |
60 | export default function (identifier) {
61 | return connectWpPost(ContentTypes.Page, identifier)(Page)
62 | }
63 |
--------------------------------------------------------------------------------
/webpack/webpack-isomorphic-tools.js:
--------------------------------------------------------------------------------
1 | /**
2 | * # Webpack Isomorphic Tools Config
3 | *
4 | * https://github.com/halt-hammerzeit/webpack-isomorphic-tools
5 | */
6 |
7 | var WebpackIsomorphicToolsPlugin = require('webpack-isomorphic-tools/plugin')
8 |
9 | module.exports = {
10 | webpack_assets_file_path: './static/dist/webpack-assets.json',
11 | assets: {
12 | images: {
13 | extensions: [
14 | 'jpeg',
15 | 'jpg',
16 | 'png',
17 | 'gif'
18 | ],
19 | parser: WebpackIsomorphicToolsPlugin.url_loader_parser
20 | },
21 | fonts: {
22 | extensions: [
23 | 'woff',
24 | 'woff2',
25 | 'ttf',
26 | 'eot'
27 | ],
28 | parser: WebpackIsomorphicToolsPlugin.url_loader_parser
29 | },
30 | style_modules: {
31 | extensions: [
32 | 'scss'
33 | ],
34 | filter: function (module, regex, options, log) {
35 | return options.development
36 | ? WebpackIsomorphicToolsPlugin.style_loader_filter(module, regex, options, log)
37 | : regex.test(module.name)
38 | },
39 | path: function (module, options, log) {
40 | return options.development
41 | ? WebpackIsomorphicToolsPlugin.style_loader_path_extractor(module, options, log)
42 | : module.name
43 | },
44 | parser: function (module, options, log) {
45 | return options.development
46 | ? WebpackIsomorphicToolsPlugin.css_modules_loader_parser(module, options, log)
47 | : module.source
48 | }
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/store/create.js:
--------------------------------------------------------------------------------
1 | /* global __DEVELOPMENT__:false, __CLIENT__:false, __DEVTOOLS__:false */
2 | /**
3 | * # Create Redux Store
4 | */
5 |
6 | import { createStore as _createStore, applyMiddleware, compose } from 'redux'
7 | import { routerMiddleware } from 'react-router-redux'
8 | import { persistState } from 'redux-devtools'
9 | import createSagaMiddleware from 'redux-saga'
10 |
11 | import { DevTools } from '../components'
12 | import rootSaga from './sagas'
13 | import reducer from './reducer'
14 |
15 | export default function createStore (history, data) {
16 | // Sync dispatched route actions to the history
17 | const reduxRouterMiddleware = routerMiddleware(history)
18 | const sagaMiddleware = createSagaMiddleware()
19 | const middleware = [sagaMiddleware, reduxRouterMiddleware]
20 |
21 | const finalCreateStore = (() => {
22 | if (__DEVELOPMENT__ && __CLIENT__ && __DEVTOOLS__) {
23 | return compose(
24 | applyMiddleware(...middleware),
25 | persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/)),
26 | window.devToolsExtension ? window.devToolsExtension() : DevTools.instrument()
27 | )(_createStore)
28 | }
29 |
30 | return compose(applyMiddleware(...middleware))(_createStore)
31 | })()
32 |
33 | const store = finalCreateStore(reducer, data)
34 |
35 | if (__DEVELOPMENT__ && module.hot) {
36 | module.hot.accept('./reducer', () => {
37 | store.replaceReducer(require('./reducer'))
38 | })
39 | }
40 |
41 | sagaMiddleware.run(rootSaga)
42 |
43 | return {
44 | ...store,
45 | runSaga: sagaMiddleware.run
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/helpers/Html.js:
--------------------------------------------------------------------------------
1 | /**
2 | * # Html
3 | *
4 | * Wrapper component containing HTML metadata and boilerplate tags.
5 | * Used in server-side code only to wrap the string output of the
6 | * rendered route component.
7 | *
8 | * The only thing this component doesn't (and can't) include is the
9 | * HTML doctype declaration, which is added to the rendered output
10 | * by the `server.js` file.
11 | */
12 |
13 | import React, { Component, PropTypes } from 'react'
14 | import ReactDOM from 'react-dom/server'
15 | import Helmet from 'react-helmet'
16 | import serialize from 'serialize-javascript'
17 |
18 | export default class Html extends Component {
19 | static propTypes = {
20 | assets: PropTypes.object,
21 | component: PropTypes.node,
22 | store: PropTypes.object,
23 | noscript: PropTypes.bool
24 | }
25 |
26 | render () {
27 | const { assets, component, store, noscript } = this.props
28 |
29 | const head = Helmet.rewind()
30 | const stylesHtml = require('../theme/global.scss')._style
31 | const dataHtml = 'window.__STATE__=' + serialize(store.getState())
32 | const content = component ? ReactDOM.renderToString(component) : ''
33 |
34 | return (
35 |
36 |
37 | {head.base.toComponent()}
38 | {head.title.toComponent()}
39 | {head.meta.toComponent()}
40 | {head.link.toComponent()}
41 | {head.script.toComponent()}
42 |
43 |
44 |
45 |
46 | {/* styles for production */}
47 | {Object.keys(assets.styles).map((style, key) =>
48 |
50 | )}
51 |
52 | {/* styles for development */}
53 | {Object.keys(assets.styles).length === 0
54 | ?
55 | : null}
56 |
57 |
58 |
59 | {!noscript && }
60 | {!noscript && }
61 |
62 |
63 | )
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
kasia-boilerplate
2 |
3 |
4 | A universal application boilerplate with
5 |
6 | Kasia
7 |
8 |
9 |
10 | Made with ❤ at @outlandish
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | An easy way to start building universal/isomorphic JavaScript applications that talk to a WordPress backend. Right on!
20 |
21 | ## What's inside?
22 |
23 | We used the [`react-redux-universal-hot-example`](https://github.com/erikras/react-redux-universal-hot-example)
24 | boilerplate as our starting point, however lots has changed since then and the two are now quite different.
25 | Thanks go to the creator and maintainers of the aforementioned repo.
26 |
27 | ```js
28 | // Tools/libraries/features
29 | Express, stage-0 ES2015, React, React Router, Redux, Kasia,
30 | Redux Sagas, Redux Dev Tools, SCSS, Webpack, Hot Reloading
31 | ```
32 |
33 | ## Getting Started
34 |
35 | ### Set up WordPress
36 |
37 | We aim to release an Ansible script which will do all of this for you, but for now...
38 |
39 | - Install wordpress at `localhost/wordpress` (or somewhere else; change accordingly in `src/config.js`)
40 | - Install and enable these plugins: [`WP API REST JSON API v2`](https://en-gb.wordpress.org/plugins/rest-api/), [`WP API MENUS`](https://en-gb.wordpress.org/plugins/wp-api-menus/)
41 | - Create two pages with slugs `homepage` and `posts`
42 | - Create a menu (Appearance > Menus) called `'primary'` containing the two pages you just created
43 | - Create some Posts
44 |
45 | ### Get the application
46 |
47 | - Clone this repo: `git clone wp-app`
48 | - Change into it: `cd wp-app`
49 | - Install dependencies: `npm install`
50 | - Configure the application in `src/config.js`
51 |
52 | ### Start the app
53 |
54 | As a developer:
55 |
56 | - `npm run build`
57 | - `npm run dev`
58 |
59 | By default the web server is at localhost:3000
60 |
61 | ______
62 |
63 | In production:
64 |
65 | - `npm run build`
66 | - `npm start`
67 |
68 | By default the web server is at localhost:8080
69 |
70 | ## Contributing
71 |
72 | All pull requests and issues welcome!
73 |
74 | - When submitting an issue please provide adequate steps to reproduce the problem.
75 | - PRs must be made using the `standard` code style.
76 |
77 | If you're not sure how to contribute, check out Kent C. Dodds'
78 | [great video tutorials on egghead.io](https://egghead.io/lessons/javascript-identifying-how-to-contribute-to-an-open-source-project-on-github)!
79 |
80 | ## Author & License
81 |
82 | `kasia-boilerplate` was created by [Outlandish](https://twitter.com/outlandish) and is released under the MIT license.
83 |
--------------------------------------------------------------------------------
/webpack/prod.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * # Webpack Production Config
3 | */
4 |
5 | require('babel-polyfill')
6 |
7 | var path = require('path')
8 | var webpack = require('webpack')
9 | var CleanPlugin = require('clean-webpack-plugin')
10 | var ExtractTextPlugin = require('extract-text-webpack-plugin')
11 | var strip = require('strip-loader')
12 |
13 | var projectRootPath = path.resolve(__dirname, '../')
14 | var assetsPath = path.resolve(__dirname, '../static/dist')
15 |
16 | var WebpackIsomorphicToolsPlugin = require('webpack-isomorphic-tools/plugin')
17 | var webpackIsomorphicToolsPlugin = new WebpackIsomorphicToolsPlugin(require('./webpack-isomorphic-tools'))
18 |
19 | var scssExtract = 'css' +
20 | '?modules' +
21 | '&importLoaders=2' +
22 | '&sourceMap' +
23 | '&sourceMap=true' +
24 | '&sourceMapContents=true' +
25 | '!autoprefixer?browsers=last 2 version' +
26 | '!sass?outputStyle=expanded'
27 |
28 | module.exports = {
29 | devtool: 'source-map',
30 | context: path.resolve(__dirname, '..'),
31 | entry: {
32 | main: [
33 | './src/client.js',
34 | './src/theme/global.scss'
35 | ]
36 | },
37 | output: {
38 | path: assetsPath,
39 | filename: '[name]-[chunkhash].js',
40 | chunkFilename: '[name]-[chunkhash].js',
41 | publicPath: '/dist/'
42 | },
43 | module: {
44 | loaders: [
45 | { test: /\.jsx?$/, exclude: /node_modules/, loaders: [strip.loader('debug'), 'babel']},
46 | { test: /\.json$/, loader: 'json-loader' },
47 | { test: /\.scss$/, loader: ExtractTextPlugin.extract('style', scssExtract) },
48 | { test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff" },
49 | { test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff" },
50 | { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/octet-stream" },
51 | { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file" },
52 | { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=image/svg+xml" },
53 | { test: webpackIsomorphicToolsPlugin.regular_expression('images'), loader: 'url-loader?limit=10240' }
54 | ]
55 | },
56 | progress: true,
57 | resolve: {
58 | modulesDirectories: [
59 | 'src',
60 | 'node_modules'
61 | ],
62 | extensions: [
63 | '',
64 | '.json',
65 | '.js',
66 | '.jsx'
67 | ]
68 | },
69 | plugins: [
70 | new CleanPlugin([assetsPath], { root: projectRootPath }),
71 | new ExtractTextPlugin('[name]-[chunkhash].css', { allChunks: true }),
72 | new webpack.DefinePlugin({
73 | 'process.env': { NODE_ENV: '"production"' },
74 | __CLIENT__: true,
75 | __SERVER__: false,
76 | __DEVELOPMENT__: false,
77 | __DEVTOOLS__: false
78 | }),
79 | new webpack.IgnorePlugin(/\.\/dev/, /\/config$/),
80 | new webpack.optimize.DedupePlugin(),
81 | new webpack.optimize.OccurenceOrderPlugin(),
82 | new webpack.optimize.UglifyJsPlugin({
83 | compress: {
84 | warnings: false
85 | }
86 | }),
87 | webpackIsomorphicToolsPlugin
88 | ]
89 | }
90 |
--------------------------------------------------------------------------------
/src/helpers/htmlParser.js:
--------------------------------------------------------------------------------
1 | import { Link } from 'react-router'
2 | import React from 'react'
3 | import entities from 'entities'
4 | import HtmlToReact from 'html-to-react'
5 | import voidElements from 'void-elements'
6 |
7 | import config from '../config'
8 |
9 | const parser = new HtmlToReact.Parser(React)
10 | const processNodeDefinitions = new HtmlToReact.ProcessNodeDefinitions(React)
11 |
12 | /**
13 | * Convert common Elements to their React equivalent.
14 | * @param {String} html HTML string
15 | * @returns {Object} HtmlToReact parser
16 | */
17 | export default function parse (html) {
18 | const counts = {}
19 | const imageContainers = ['div', 'p']
20 |
21 | const processingInstructions = [{
22 | shouldProcessNode: (node) => true,
23 | processNode: (node, children) => {
24 | if (!counts[node.name]) {
25 | counts[node.name] = 0
26 | }
27 |
28 | counts[node.name]++
29 |
30 | const attrs = {
31 | key: node.name + '-' + counts[node.name],
32 | ...node.attribs
33 | }
34 |
35 | if (
36 | imageContainers.indexOf(node.name) !== -1 &&
37 | children && children.length
38 | ) {
39 | const img = children.find(child => child.type === 'img')
40 | const index = children.indexOf(img)
41 |
42 | if (index !== -1) {
43 | return createImageContainer(node, attrs, children, index)
44 | }
45 | }
46 |
47 | if (node.type === 'text') {
48 | return entities.decodeHTML(node.data)
49 | }
50 |
51 | if (node.name === 'img') {
52 | attrs.style = { clear: 'both' }
53 |
54 | if (attrs.class) {
55 | const classes = attrs.class.split(' ')
56 |
57 | if (classes.indexOf('alignleft') !== -1) {
58 | attrs.style.float = 'left'
59 | } else if (classes.indexOf('alignright') !== -1) {
60 | attrs.style.float = 'right'
61 | }
62 |
63 | attrs.class = ''
64 | }
65 |
66 | return React.createElement(node.name, attrs, node.data)
67 | }
68 |
69 | if (
70 | node.name == 'a' &&
71 | // Ignore `mailto:` links
72 | attrs.href.indexOf('mailto') === -1 &&
73 | // Ignore any href that is not pointing to our WP instance
74 | attrs.href.indexOf(config.wordpress) === 0
75 | ) {
76 | attrs.href = ''
77 | return {children}
78 | }
79 |
80 | if (Object.keys(voidElements).indexOf(node.name) !== -1) {
81 | return React.createElement(node.name, attrs, node.data)
82 | }
83 |
84 | return processNodeDefinitions.processDefaultNode(node, children)
85 | }
86 | }]
87 |
88 | return parser.parseWithInstructions(
89 | `${html}
`,
90 | (node) => true,
91 | processingInstructions
92 | )
93 | }
94 |
95 | /**
96 | * Replace an image with a React component, excluding
97 | * most of the attributes set by WordPress.
98 | * @returns {Element}
99 | */
100 | function createImageContainer (container, containerAttrs, children, index) {
101 | const img = children[index]
102 |
103 | children[index] = React.createElement('img', {
104 | alt: img.props.alt,
105 | src: img.props.src
106 | })
107 |
108 | containerAttrs.className += ' contains-img'
109 | containerAttrs.style = ''
110 |
111 | return React.createElement(container.name, containerAttrs, children)
112 | }
113 |
--------------------------------------------------------------------------------
/src/server.js:
--------------------------------------------------------------------------------
1 | /* global global:false, __dirname:false, __DEVELOPMENT__:false */
2 | /**
3 | * # Server
4 | */
5 |
6 | import { createMemoryHistory } from 'history'
7 | import { match, useRouterHistory } from 'react-router'
8 | import { syncHistoryWithStore } from 'react-router-redux'
9 | import path from 'path'
10 | import http from 'http'
11 | import pify from 'pify'
12 | import React from 'react'
13 | import Express from 'express'
14 | import favicon from 'serve-favicon'
15 | import compression from 'compression'
16 | import PrettyError from 'pretty-error'
17 | import ReactDOM from 'react-dom/server'
18 |
19 | import {
20 | makePreloaderSaga as makeWpPreloaderSaga
21 | } from 'kasia/util'
22 |
23 | import {
24 | fetchThemeLocation,
25 | makePreloader as makeWpMenusPreloaderSaga
26 | } from 'kasia-plugin-wp-api-menus'
27 |
28 | import { Root } from './containers'
29 | import { Error } from './components'
30 | import { Html, runSagas } from './helpers'
31 | import { createStore, WP } from './store'
32 | import config from './config'
33 | import routes from './routes'
34 |
35 | if (!config.port) {
36 | console.error('==> ERROR: No port environment variable')
37 | console.error('==> ERROR: Exiting...')
38 | process.exit()
39 | }
40 |
41 | const app = new Express()
42 | const pretty = new PrettyError()
43 | const server = new http.Server(app)
44 |
45 | app.use(compression())
46 | app.use(favicon(path.join(__dirname, '../static/favicon.ico')))
47 | app.use(Express.static(path.join(__dirname, '..', 'static')))
48 |
49 | app.use((req, res) => {
50 | if (__DEVELOPMENT__) {
51 | global.webpackIsomorphicTools.refresh()
52 | }
53 |
54 | const location = req.originalUrl
55 | const history = useRouterHistory(createMemoryHistory)({})
56 | const store = createStore(history, {})
57 |
58 | syncHistoryWithStore(history, store)
59 | history.replace(req.originalUrl)
60 |
61 | pify(match, { multiArgs: true })({ history, routes, location })
62 | .then((result) => {
63 | const [ redirectLocation, renderProps ] = result
64 |
65 | if (redirectLocation) {
66 | res.redirect(redirectLocation.pathname + redirectLocation.search)
67 | return
68 | }
69 |
70 | if (!renderProps) {
71 | res.status(404).send('Not found')
72 | return
73 | }
74 |
75 | const preloaders = [
76 | makeWpPreloaderSaga(renderProps.components, renderProps),
77 | makeWpMenusPreloaderSaga(WP, fetchThemeLocation('primary'))
78 | ]
79 |
80 | return runSagas(store, preloaders).then(() => {
81 | const rootProps = { renderProps, history, routes, store }
82 | const root =
83 |
84 | global.navigator = { userAgent: req.headers['user-agent'] }
85 |
86 | const document = ReactDOM.renderToString(
87 |
91 | )
92 |
93 | res.send('\n' + document)
94 | })
95 | })
96 | .catch((error) => {
97 | console.error('==> ROUTER ERROR:', pretty.render(error))
98 |
99 | const document = ReactDOM.renderToString(
100 |