Code Monkey home page Code Monkey logo

react-isomorphic-boilerplate's Introduction

React Isomorphic Boilerplate

An universal React isomorphic boilerplate for building server-side render web app.

dependencies Status devDependencies Status PRs Welcome npm

Introduction

This repository is an universal React isomorphic boilerplate for developer to quickly build a super fast and powerful web app that can be rendered both on the client and on the server using the most cutting-edge technology. Compared to others, this boilerplate has more pithily and more elegant configuration file based on environment variables, one for development, one for production. In addition, the directory structure is organized corresponding to mvc principle aim at the best practice.

Technology Stack

Getting Started

  • Require Node.js v6 or later.
  • npm install to install dependencies and devDependencies.
  • npm run dev to start up the development environment.
  • npm run build to compile and bundle the client and server files.
  • npm start to deploy the production server.

What's included

react-isomorphic-boilerplate/                // root directory
├── build/                                   // webpack config directory
│     ├── webpack.dev.config.js                 // config for webpack when run development bundle
│     └── webpack.prod.config.js                // config for webpack when run production bundle
├── client/                                  // client directory
│     ├── about/                                // `about` module
│     ├── common/                               // `common` module
│     ├── home/                                 // `home` module
│     ├── shared/                               // shared module
│     ├── explore/                              // `explore` module
│     ├── index.js                              // client entry file
│     └── routes.js                             // client route config
├── dist/                                    // bundle output directory
│     ├── client/                               // the client side output directory
│     └── server/                               // the server side output directory
├── server/                                  // server directory
│     ├── controllers/                          // controllers in server
│     ├── middlewares/                          // custom middlewares in server
│     ├── models/                               // models in server
│     ├── routes/                               // routes in server
│     ├── app.js                                // create koa instance in server
│     ├── server.dev.js                         // entry file in development mode
│     └── server.prod.js                        // entry file in production mode
├── views/                                   // views directory
│     ├── dev/                                  // output files generated by tpl in development mode
│     ├── prod/                                 // output files generated by tpl in production mode
│     └── tpl                                   // templates injected by html-webpack-plugin
├── .editorconfig                            // uniform the text editor behavior
├── .eslintignore                            // which paths should be omitted from linting
├── .eslintrc.json                           // specific rules for eslint
├── .gitattributes                           // uniform the new line perform behavior
├── .gitignore                               // ignore the specific files when using git
├── LICENSE                                  // license information
├── package.json                             // npm entry file
├── README.md                                // just what you see now
└── yarn.lock                                // lock file autogenerated by yarn

Why Isomorphic

SEO

An application that can only run in the client-side cannot serve HTML to crawlers, so it will have poor SEO by default. Web crawlers function by making a request to a web server and interpreting the result. but if the server returns a blank page, it’s not of much value. There are workarounds, but not without jumping through some hoops.

Performance

By the same token, if the server doesn’t render a full page of HTML but instead waits for client-side JavaScript to do so, users will experience a few critical seconds of blank page or loading spinner before seeing the content on the page. There are plenty of studies showing the drastic effect a slow site has on users, and thus revenue.

Maintainability

While the ideal case can lead to a nice, clean separation of concerns, inevitably some bits of application logic or view logic end up duplicated between client and server, often in different languages. Common examples are date and currency formatting, form validations, and routing logic. This makes maintenance a nightmare, especially for more complex apps.

Problem exists yet

It happens when run in development mode. This is caused by deprecated using extract-text-webpack-plugin in development for getting a seamless hmr experience.(Why deprecated? See this Issue) If you are not an OCD, go ahead, ignore it.

Mismatch

It happens also when run in development mode. This is caused by when you update the react component code and reload the page, the markup generated mismatches that on server render. However, once you restart the server, the checksum will be valid. So it is harmless, ignore it also.

Links

http://www.jianshu.com/p/0ecd727107bb

License

MIT

react-isomorphic-boilerplate's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-isomorphic-boilerplate's Issues

服务端请求数据问题

请问这个在服务端是如何请求数据的,看了半天没明白,
因为window.REDUX_STATE 一直是空的

小白求问要怎么访问后台接口api,想通过koa发请求

async function getMessageList(ctx) {
ctx.url=HOST+'/message/query';
ctx.method='POST';
ctx.query= {


userId:'13145674567',
entId:'2',
startDate:'2016-10-10',
endDate:''
};
let messages =ctx.request.body || 0;
ctx.body = "you post data:"+JSON.stringify({messages:messages});
}

比如这个,配置了server端的routes,不知道controllers该怎么写

Redux无法热更新

看了作者的例子受益匪浅,我刚入门webpack,做练习的过程中我也自己用router4+react-loadable仿写了一个demo,但在reducer更改初始值时无法热更新,这是我的demo地址:https://github.com/wd2010/webpack-demo,请大神帮忙看看!

app.js(entry文件)

import React from 'react';
import {hydrate} from 'react-dom';
import configureStore from './store/configureStore';
import createHistory from 'history/createBrowserHistory'
import createApp from './store/createApp';
import Loadable from 'react-loadable';

const initialState = window.__INITIAL_STATE__;
let store=configureStore(initialState)
const history=createHistory()

const render=()=>{
  let application=createApp({store,history});
  hydrate(application,document.getElementById('root'))
}

window.main = () => {
  Loadable.preloadReady().then(() => {
    render()
  });
};

configureStore.js

import {createStore, applyMiddleware,compose} from "redux";
import thunkMiddleware from "redux-thunk";
import createHistory from 'history/createMemoryHistory';
import {  routerReducer, routerMiddleware } from 'react-router-redux'
import {composeWithDevTools} from 'redux-devtools-extension'
import rootReducer from './reducers/index';
const routerReducers=routerMiddleware(createHistory());//路由
const middleware=[thunkMiddleware,routerReducers];

let configureStore=(initialState)=>{
  let store=createStore(rootReducer,initialState,composeWithDevTools(applyMiddleware(...middleware)));
  //热加载配置
if(module.hot) {
    console.log(module.hot.status())
    module.hot.accept('./reducers/index.js', () => {
        store.replaceReducer(rootReducer)
      console.log('=',store.getState())
    });
  }
  return store;
}

export default configureStore;

webpack.config.js

const  ExtractTextWebpackPlugin = require("extract-text-webpack-plugin"),
  HtmlWebpackPlugin=require('html-webpack-plugin'),
  ProgressBarPlugin = require('progress-bar-webpack-plugin');
const path=require('path');
const webpack=require('webpack');
import { ReactLoadablePlugin } from 'react-loadable/webpack';
const DIR_NAME=path.join(__dirname,'../');

const config={
  context: DIR_NAME,
  entry:{
    bundle: ['./client/app.js','webpack-hot-middleware/client?path=/__webpack_hmr'],
    vendor: ['react','react-dom','redux','react-redux'],
  },
  output:{
    path:  path.resolve(DIR_NAME,'./dist/client'),
    filename: '[name].js',
    chunkFilename: 'chunk.[name].js',
    publicPath:'/'
  },

  resolve: {extensions: ['.js', '.jsx' ,'.json', '.scss']},
  module:{
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader:'babel-loader',
          options:{
            presets: ['env', 'react', 'stage-0','react-hmre'],
            plugins: ['transform-runtime', 'add-module-exports','syntax-dynamic-import','react-loadable/babel'],
            cacheDirectory: true,
          }
        }
      },{
        test: /\.html$/,
        exclude:/node_modules/,
        loader: 'html-loader'
      }
    ],
  },
  plugins:[
    new webpack.optimize.OccurrenceOrderPlugin(),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoEmitOnErrorsPlugin(),
    new ProgressBarPlugin({summary: false}),
    new webpack.optimize.CommonsChunkPlugin({
      names:['vendor','manifest'],
      filename:'[name].js',
    }),
    new HtmlWebpackPlugin({
      filename: '../views/dev/index.html',
      template: './views/tpl/index.html',
      chunks: ['vendor','manifest', 'bundle'],
    }),
    new ReactLoadablePlugin({
      filename: './views/dev/react-loadable.json',

    }),
  ],

}


module.exports=config

clientRouter.js

import React from 'react';
import fs from 'fs';
import path from 'path';
import {renderToString} from 'react-dom/server';
import Loadable from 'react-loadable';
import { getBundles } from 'react-loadable/webpack';
import stats from '../../views/dev/react-loadable.json';
import {routesConfig} from '../../client/routes';
import configureStore from '../../client/store/configureStore';
import createHistory from 'history/createMemoryHistory'
import createApp from '../../client/store/createApp';
import {matchPath} from 'react-router-dom';
import {matchRoutes} from 'react-router-config';
import Helmet from 'react-helmet';

const store=configureStore();


const prepHTML=(data,{html,head,body,scripts,styles,state})=>{
  data=data.replace('<html',`<html ${html}`);
  data=data.replace('</head>',`${head} \n ${styles}</head>`);
  data=data.replace('<div id="root"></div>',`<div id="root">${body}</div>`);
  data=data.replace('<body>',`<body> \n <script>window.__INITIAL_STATE__ =${JSON.stringify(state)}</script>`);
  data=data.replace('</body>',`${scripts}</body>`);
  return data;
}

const markup=({ctx})=>{
  let modules = [];
  const history=createHistory({initialEntries:[ctx.req.url]})
  let state=store.getState();
  let appString= renderToString(
    <Loadable.Capture report={moduleName => modules.push(moduleName)}>
      {createApp({store,history})}
    </Loadable.Capture>
  )

  let bundles = getBundles(stats, modules);
  let scriptfiles = bundles.filter(bundle => bundle.file.endsWith('.js'));
  let stylefiles = bundles.filter(bundle => bundle.file.endsWith('.css'));

  let scripts=scriptfiles.map(script=>`<script src="/${script.file}"></script>`).join('\n');
  let styles=stylefiles.map(style=>`<link href="/${style.file}" rel="stylesheet"/>`).join('\n');

  let filePath=path.join(__dirname,'../../views/dev/index.html')
  let htmlData=fs.readFileSync(filePath,'utf-8');

  const helmet=Helmet.renderStatic();

  let renderedHtml=prepHTML(htmlData,{
    html:helmet.htmlAttributes.toString(),
    head:helmet.title.toString()+helmet.meta.toString()+helmet.link.toString(),
    body:appString,
    scripts:scripts,
    styles:styles,
    state
  })

  return renderedHtml
}

const getMatch=(routesArray, url)=>{
  return routesArray.some(router=>matchPath(url,{
    path: router.props.path,
    exact: router.props.exact,
  }))
}

const clientRouter=async(ctx,next)=>{
  let isMatch=getMatch(routesConfig,ctx.req.url);

  if(isMatch){
    let renderedHtml=markup({ctx});
    ctx.body=renderedHtml
  }else{
    await next()
  }
}

export default clientRouter;

更改reducer初始值时,console.log输出值

[HMR] connected
    bundle.js:6781 [HMR] bundle rebuilding
    bundle.js:6789 [HMR] bundle rebuilt in 180ms
    bundle.js:7715 [HMR] Checking for updates on the server...
    bundle.js:7788 [HMR] Updated modules:
    bundle.js:7790 [HMR]  - ./client/store/reducers/home.js
    bundle.js:7790 [HMR]  - ./client/store/reducers/index.js
    bundle.js:7795 [HMR] App is up to date.
    bundle.js:6781 [HMR] bundle rebuilding
    bundle.js:6789 [HMR] bundle rebuilt in 195ms
    bundle.js:7715 [HMR] Checking for updates on the server...
    bundle.js:7788 [HMR] Updated modules:
    bundle.js:7790 [HMR]  - ./client/store/reducers/home.js
    bundle.js:7790 [HMR]  - ./client/store/reducers/index.js
    bundle.js:7795 [HMR] App is up to date.
    bundle.js:6781 [HMR] bundle rebuilding
    bundle.js:6789 [HMR] bundle rebuilt in 175ms
    bundle.js:7715 [HMR] Checking for updates on the server...
    bundle.js:7788 [HMR] Updated modules:
    bundle.js:7790 [HMR]  - ./client/store/reducers/home.js
    bundle.js:7790 [HMR]  - ./client/store/reducers/index.js
    bundle.js:7795 [HMR] App is up to date.

getUrlParams is not defined

运行环境

OSX(10.12)
node(v6.9.4)
npm(3.10.10)

错误截图

[nodemon] starting `node ./server/server.dev.js`
/Users/****/Workspace/react-isomorphic-boilerplate/client/shared/utils.js:61
    getUrlParams: getUrlParams
                  ^

ReferenceError: getUrlParams is not defined
    at Object.<anonymous> (/Users/****/Workspace/react-isomorphic-boilerplate/client/shared/utils.js:51:5)
    at Module._compile (module.js:570:32)
    at loader (/Users/****/Workspace/react-isomorphic-boilerplate/node_modules/babel-register/lib/node.js:144:5)
    at Object.require.extensions.(anonymous function) [as .js] (/Users/****/Workspace/react-isomorphic-boilerplate/node_modules/babel-register/lib/node.js:154:7)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.require (module.js:497:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (/Users/****)/Workspace/react-isomorphic-boilerplate/client/common/actions/index.js:2:1)
    at Module._compile (module.js:570:32)
    at loader (/Users/****/Workspace/react-isomorphic-boilerplate/node_modules/babel-register/lib/node.js:144:5)
    at Object.require.extensions.(anonymous function) [as .js] (/Users/****/Workspace/react-isomorphic-boilerplate/node_modules/babel-register/lib/node.js:154:7)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)

代码截图

function getURLParams() {
    const search = location.search.slice(1),
        rParam = /([^&]*)=([^&]*)/g
    let ret = {},
        param

    while (param = rParam.exec(search)) {
        ret[param[1]] = param[2]
    }

    return ret
}

export default {
    ajax,
    getURLParams
}

getURLParams原本为getUrlParams,修改为getURLParams后可以成功运行。

根据模板生成服务器端字符串后 如何更新js或css缓存

请问服务器端生成react渲染后的字符串 然后和模板混合 send到客户端上,请问 模板上的js 和css文件名字都是写死的 第一次发布完毕后 再次发布会有缓存 如何解决这个问题呢,如果客户端不清空缓存则新的样式或js不生效呀

typo

Hi guys,

There some typo error for the description of this repo, I'd like to change it to the one blow, please have a check. Thanks.

😇😇😇 A universal React isomorphic boilerplate for building server-side render web app http://www.jianshu.com/p/0ecd727107bb

全局加载组件

我想在全局加载一个组件,比如底部导航栏
我把它放在client/common/containers/Common.js里,render里代码如下
`



{Children.map(children, child =>
cloneElement(child, {...props})
)}

` 在dev下显示和操作都是没有问题的,但是build完,npm start运行的时候报错 cannot read property 'shape' of undefined cannot read property 'call' of undefined 这应该怎么解决呢?

Crawler is unable to access data

After retrieving the data,
When going to the page source(command + option + U) it is showing {userinfo: null}. Can you please tell why this is happening.

在开发模式下的一些问题

按着你的思路正在实现一个 boilerplate,发现一些问题,nodemon 只是在监听 server 目录下的代码,但是 server 也承担着服务器渲染的工作,会用到 client 下的代码,所以说会出现下面的一些问题:

  1. 本来客户端代码就有错误,此时运行服务器会崩溃,此时就算改正 client 下的代码也没有用,因为 nodemon 监听的是 server 下的代码,这时必须自己重启服务器
  2. 正常运行之后,修改客户端代码可以实现热替换,但如果刷新一个页面,会从服务器端拉取数据,服务器端返回的还是之前没有修改过代码生成的html,接着React 会发现服务器端返回的跟客户端返回的出现不一致,发出一个警告,如下:

2017-01-05 11 29 42

更新:

看了一下 README 提到了上面的问题,这个问题没有什么解决办法么?😑

server/middlewares/clientRoute.js中路由匹配match()函数问题前是否要加await操作符?

match({routes,location:ctx.url},(error,redirectLocation,renderProps)=>{ _renderProps=renderProps })
改为:
await match({routes,location:ctx.url},(error,redirectLocation,renderProps)=>{ _renderProps=renderProps })
我使用你个这脚手架,学习开发一个小项目,运行npm run dev ,浏览器可以正常访问。
但是构建后,执行npm start,页面提示Not found!
经过调试发现,在match函数回调中为_renderProps赋值。在match返回前继续往下运行,提示not found。
奇怪的是开发模式下正常,这是为什么呢?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.