代码地址以下:
http://www.demodashi.com/demo/12575.htmlhtml
Web前端世界突飞猛进变化太快,为了让本身跟上节奏不掉队,总结出了本身的一套React脚手架,方便往后新项目能够基于此快速上手开发。前端
源码: https://github.com/54sword/react-starternode
node ^8.6.0 npm ^5.7.1
没有在windows机器上测试过,可能会报错react
$ git clone git@github.com:54sword/react-starter.git $ cd react-starter $ npm install $ npm run dev
浏览器打开 http://localhost:4000webpack
注意:开发环境下,代码不分片,生产环境下才会分片git
npm run dev
npm run dist npm run server
一、修改 config/index.js 中的 public_path 配置
二、打包文件,除了index.ejs是服务端渲染的模版文件,其余都是客户端使用的文件github
npm run dist
三、将项目上传至你的服务器
四、启动服务web
Node 启动服务shell
NODE_ENV=production __NODE__=true BABEL_ENV=server node src/server
或使用 pm2 启动服务express
NODE_ENV=production __NODE__=true BABEL_ENV=server pm2 start src/server --name "react-starter" --max-memory-restart 400M
. ├── config # 项目配置文件 ├── dist # 全部打包文件储存在这里 ├── src # 程序源文件 │ ├── actions # redux actions │ ├── client # 客户端入口 │ ├── common # 全局可复用的容器组件 │ ├── components # 全局可复用的容器组件 │ ├── pages # 页面组件 │ ├── reducers # redux reducers │ ├── router # 路由配置 │ ├── server # 服务端入口 │ ├── store # redux store │ └── view # html模版文件 ├── .babelrc # 程序源文件 ├── webpack.development.config.js # 开发环境的webpack配置项 └── webpack.profuction.config.js # 生产环境的wbepakc配置项
src/router/index.js 为路由配置文件,以下代码是一个路由项的配置说明
{ // 路径 path: '/', // 若是为true,则只有在路径彻底匹配location.pathname时才匹配 exact: true, // 页面头部组件 head: Head, /** * 内容组件(页面主要内容) * generateAsyncRouteComponent 为生成一个异步加载组件, * 客户端打包的时候 ../pages/home,会将该组件单独打包成一个js文件,用于在客户端按需加载。 */ component: generateAsyncRouteComponent({ loader: () => import('../pages/home') }), /** * 进入该页面的触发事件 * requireAuth 为须要登陆才能访问 * requireTourists 只有游客能够访问 * triggerEnter 进入事件,能够用做任何人均可以访问 */ enter: requireAuth }
src/pages/ 为页面组件,实现具体的页面内容,以首页为例的说明 ./src/pages/home/index.js
import React from 'react'; import PropTypes from 'prop-types'; // 加载帖子列表的方法 import { loadPostsList } from '../../actions/posts'; // http://blog.csdn.net/ISaiSai/article/details/78094556 import { withRouter } from 'react-router-dom'; // 壳组件,给页面组件套一个壳组件,方便给全部页面增长额外功能和属性 import Shell from '../../components/shell'; // 生成页面Meta,如标题、描述、关键词 import Meta from '../../components/meta'; // 帖子列表组件 import PostsList from '../../components/posts/list'; export class Home extends React.Component { // 服务端渲染 // 加载须要在服务端渲染的数据 static loadData({ store, match }) { return new Promise(async function (resolve, reject) { /** * 这里的 loadPostsList 方法,是在服务端加载 posts 数据,储存到 redux 中。 * 这里对应的组件是 PostsList,PostsList组件里面也有 loadPostsList 方法,但它是在客户端执行。 * 而后,服务端在渲染 PostsList 组件的时候,咱们会先判断若是redux中,是否存在该条数据,若是存在,直接拿该数据渲染 */ await loadPostsList({ id: 'home', filter: { sort_by: "create_at", deleted: false, weaken: false } })(store.dispatch, store.getState); resolve({ code:200 }); }) } constructor(props) { super(props); } render() { return(<div> <Meta title="首页" /> <PostsList id={'home'} filter={{ sort_by: "create_at", deleted: false, weaken: false }} /> </div>) } } Home = withRouter(Home); export default Shell(Home);
import path from 'path'; import express from 'express'; import bodyParser from 'body-parser'; import cookieParser from 'cookie-parser'; import compress from 'compression'; // 服务端渲染依赖 import React from 'react'; import ReactDOMServer from 'react-dom/server'; import { StaticRouter, matchPath } from 'react-router'; import { Provider } from 'react-redux'; import DocumentMeta from 'react-document-meta'; // 路由配置 import configureStore from '../store'; // 路由组件 import createRouter from '../router'; // 路由初始化的redux内容 import { initialStateJSON } from '../reducers'; import { saveAccessToken, saveUserInfo } from '../actions/user'; // 配置 import { port, auth_cookie_name } from '../../config'; import sign from './sign'; import webpackHotMiddleware from './webpack-hot-middleware'; const app = express(); // ***** 注意 ***** // 不要改变以下代码执行位置,不然热更新会失效 // 开发环境开启修改代码后热更新 if (process.env.NODE_ENV === 'development') webpackHotMiddleware(app); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(cookieParser()); app.use(compress()); app.use(express.static(__dirname + '/../../dist')); // 登陆、退出 app.use('/sign', sign()); app.get('*', async (req, res) => { // 建立 store const store = configureStore(JSON.parse(initialStateJSON)); let user = null; let accessToken = req.cookies[auth_cookie_name] || ''; // 验证 token 是否有效 if (accessToken) { // 这里能够去查询 accessToken 是否有效 // your code // 这里假设若是有 accessToken ,那么就是登陆用户,将他保存到redux中 user = { id: '001', nickname: accessToken }; // 储存用户信息 store.dispatch(saveUserInfo({ userinfo: user })); // 储存access token store.dispatch(saveAccessToken({ accessToken })); } // 建立路由,返回 list 、dom // list 是路由的配置列表,dom render的dom const router = createRouter(user); const _Router = router.dom; let _route = null, _match = null; // 从路由配置列表中,找到对应的路由 router.list.some(route => { let match = matchPath(req.url.split('?')[0], route); if (match && match.path) { _route = route; _match = match; return true; } }) /** * 加载异步组件,并在异步组件中执行 loadData,loadData 加载的数据,储存到redux store中 */ const context = await _route.component.load({ store, match: _match }); // 渲染页面 let html = ReactDOMServer.renderToString( <Provider store={store}> <StaticRouter location={req.url} context={context}> <_Router /> </StaticRouter> </Provider> ); // 将redux state 转换成 json 储存到页面中 let reduxState = JSON.stringify(store.getState()).replace(/</g, '\\x3c'); // 获取页面的meta,嵌套到模版中 // 给客户端 initState let meta = DocumentMeta.renderAsHTML(); if (context.code == 301) { res.writeHead(301, { Location: context.url }); } else { res.status(context.code); res.render('../dist/index.ejs', { html, reduxState, meta }); } res.end(); }); app.listen(port); console.log('server started on port ' + port);
自制的React同构脚手架
代码地址以下:
http://www.demodashi.com/demo/12575.html
注:本文著做权归做者,由demo大师代发,拒绝转载,转载须要做者受权