代碼地址以下:
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大師代發,拒絕轉載,轉載須要做者受權