}
38 | * @private
39 | */
40 | function _request(params) {
41 | let requestUrl;
42 | let requestParams;
43 | if (typeof params === 'string') {
44 | requestUrl = params;
45 | requestParams = {};
46 | } else {
47 | const {url, ...restParams} = params;
48 | requestUrl = url;
49 | requestParams = restParams;
50 | }
51 |
52 | let rawResponse;
53 | return fetch(requestUrl, {...defaultParams, ...requestParams})
54 | .then((response) => {
55 | rawResponse = response;
56 | return response.json();
57 | })
58 | .then((json) => {
59 | if (json && json.error) {
60 | return throwApiError(requestUrl, json.error, rawResponse.status);
61 | }
62 | return json;
63 | })
64 | .catch((error) => throwApiError(requestUrl, error.message));
65 | }
66 |
67 | // application api
68 |
69 | function getStats() {
70 | return _request(`${endpoint}/stats`);
71 | }
72 |
73 | function addServer(payload) {
74 | return _request({
75 | url: `${endpoint}/servers`,
76 | method: 'POST',
77 | headers: new Headers({'content-type': 'application/json'}),
78 | body: JSON.stringify(payload),
79 | ...defaultParams
80 | });
81 | }
82 |
83 | export default {
84 | getStats,
85 | addServer,
86 | setEndpoint
87 | };
88 |
--------------------------------------------------------------------------------
/src/api/reducer.js:
--------------------------------------------------------------------------------
1 | import {List, Map, OrderedMap} from 'immutable';
2 |
3 | import {API_DATA_SERVERS_LOADED, API_REQUEST_FINISHED, API_REQUEST_STARTED} from 'api/actions';
4 |
5 | const initialState = Map({
6 | loading: false,
7 | requests: OrderedMap({}),
8 | errors: Map({
9 | last: null
10 | }),
11 | lastUpdate: Map({
12 | servers: null
13 | }),
14 | data: Map({
15 | servers: List()
16 | })
17 | });
18 |
19 | export default function ApiReducer(state = initialState, action) {
20 | switch (action.type) {
21 | case API_REQUEST_STARTED:
22 | return state.setIn(['requests', action.payload.requestId], action.payload).set('loading', true);
23 |
24 | case API_REQUEST_FINISHED:
25 | return state
26 | .removeIn(['requests', action.payload.requestId])
27 | .set('loading', state.get('requests').size > 1)
28 | .setIn(
29 | ['errors', 'last'],
30 | action.payload.error ? action.payload.error.message : state.getIn(['errors', 'last'])
31 | );
32 |
33 | case API_DATA_SERVERS_LOADED:
34 | return state
35 | .setIn(['lastUpdate', 'servers'], Date.now())
36 | .setIn(['data', 'servers'], List(action.payload.servers));
37 |
38 | default:
39 | return state;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/client.js:
--------------------------------------------------------------------------------
1 | import 'babel-polyfill';
2 | import 'whatwg-fetch';
3 | import Loadable from 'react-loadable';
4 | import intl from 'intl';
5 | import React from 'react';
6 | import ReactDOM from 'react-dom';
7 | import { Provider } from 'mobx-react';
8 | // import api from 'api/index';
9 | import allStore from './store';
10 | import { Router } from 'react-router-dom';
11 | import App from './App';
12 | import combineServerData from './helpers/combineServer';
13 | import browserHistory from './helpers/history';
14 | // global styles
15 | import 'antd/dist/antd.less';
16 |
17 | // apply polyfill
18 | if (!window.Intl) {
19 | window.Intl = intl;
20 | }
21 |
22 | // api.setEndpoint('/api');
23 | combineServerData(allStore, window.__INITIAL_STATE__);
24 | const renderApp = () => {
25 | Loadable.preloadReady().then(() => {
26 | ReactDOM.hydrate(
27 |
28 |
29 |
30 |
31 | ,
32 | document.getElementById('app')
33 | )
34 | });
35 | }
36 |
37 | renderApp();
--------------------------------------------------------------------------------
/src/components/Article/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Article = () => (
4 | MN2018-09-19T07:52:12.383Z - info: Application env: development
5 | 2018-09-19T07:52:12.947Z - info: HTTP server is now running on http://localhost:3001
6 | webpack built 2400ef2b021b433f8527 in 5849ms
7 | 2018-09-19T07:52:18.328Z - info: Hash: 2400ef2b021b433f8527
8 | Version: webpack 4.8.1
9 | Time: 5849ms
10 | Built at: 2018-09-19 15:52:18
11 | Asset Size Chunks Chunk Names
12 | imgs/BiazfanxmamNRoxxVxka.fda383.png 51.9 KiB [emitted]
13 | main.js 146 KiB main [emitted] main
14 | vendor.chunk.js 10 MiB vendor [emitted] vendor
15 | client.html 626 bytes [emitted]
16 | Entrypoint main = vendor.chunk.js main.js
17 |
18 | + 830 hidden modules
19 | Child html-webpack-plugin for "client.html":
20 | Asset Size Chunks Chunk Names
21 | client.html 27.9 KiB 0
22 | Entrypoint undefined = client.html
23 | [./node_modules/html-webpack-plugin/lib/loader.js!./src/index.html] 569 bytes {0} [built]
24 | 2018-09-19T07:52:18.329Z - info: Compiled successfully.
25 | 2018-09-19T07:52:18.484Z - info: ::1 - GET /article - 200 - 0.055s
26 | 2018-09-19T07:52:18.531Z - info: ::1 - GET /[object%20Object] - 200 - 0.019s
27 | 2018-09-19T07:52:18.544Z - info: ::1 - GET /vendor.chunk.js - 304 - 0.013s
28 | 2018-09-19T07:52:18.548Z - info: ::1 - GET /main.js - 200 - 0.001s
29 | 2018-09-19T07:52:19.492Z - info: ::1 - GET /imgs/BiazfanxmamNRoxxVxka.fda383.png - 200 - 0.001s
30 | 2018-09-19T07:52:19.676Z - info: ::1 - GET /favicon.ico - 200 - 0.012s
31 | 2018-09-19T07:52:21.560Z - info: ::1 - GET /favicon.ico - 304 - 0.016s
32 |
33 | );
34 |
35 | export default Article;
--------------------------------------------------------------------------------
/src/components/CssModule/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { observer, inject } from 'mobx-react';
3 | import styles from './index.scss'
4 |
5 | @inject('appStore')
6 | @observer
7 | export default class CssModule extends Component {
8 | render() {
9 | return (
10 |
11 | MIKOLC
12 | Hello world!
13 | Store: {this.props.appStore.name}
14 |
15 | );
16 | }
17 | }
--------------------------------------------------------------------------------
/src/components/CssModule/index.scss:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | background: red;
3 | }
4 | .name {
5 | font-size: 2em;
6 | font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif
7 | }
8 | .store {
9 | margin-left: .5em;
10 | font-weight: bold;
11 | }
--------------------------------------------------------------------------------
/src/components/LoginForm/index.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import Form from 'antd/lib/form';
3 | import Icon from 'antd/lib/icon';
4 | import Input from 'antd/lib/input';
5 | import Button from 'antd/lib/button';
6 | import Checkbox from 'antd/lib/checkbox';
7 | const FormItem = Form.Item;
8 | import styles from './index.scss';
9 |
10 | class LoginForm extends Component {
11 | handleSubmit = (e) => {
12 | e.preventDefault();
13 | this.props.form.validateFields((err, values) => {
14 | if (!err) {
15 | console.log('Received values of form: ', values);
16 | }
17 | });
18 | };
19 | render() {
20 | const {getFieldDecorator} = this.props.form;
21 | return (
22 |
55 | );
56 | }
57 | }
58 |
59 | export default Form.create()(LoginForm);
60 |
--------------------------------------------------------------------------------
/src/components/LoginForm/index.scss:
--------------------------------------------------------------------------------
1 | .form_box {
2 | width: 300px;
3 | margin: 10% auto 0 auto;
4 | .login_form_button {
5 | width: 100%;
6 | border-radius: 0;
7 | }
8 | input[class='ant-input'] {
9 | border-radius: 0;
10 | }
11 | }
--------------------------------------------------------------------------------
/src/components/NavBar/Nav/index.js:
--------------------------------------------------------------------------------
1 | // Dead simple component for the hello world (hi mom!)
2 |
3 | import React from 'react';
4 | import styles from './index.scss';
5 | import {observer, inject} from 'mobx-react';
6 | import PropTypes from 'prop-types';
7 | import Menu from 'antd/lib/menu';
8 | import Dropdown from 'antd/lib/dropdown';
9 | import Icon from 'antd/lib/icon';
10 | import img from '../../../imgs/BiazfanxmamNRoxxVxka.png';
11 |
12 | function Nav() {
13 | const menu = (
14 |
28 | );
29 | return (
30 |
31 | -
32 |
33 |
34 |
35 |
36 |
37 |
Serati Ma
38 |
39 |
40 |
41 |
42 | );
43 | }
44 | Nav.propTypes = {
45 | homeStore: PropTypes.object,
46 | appStore: PropTypes.object
47 | };
48 | export default inject('homeStore', 'appStore')(observer(Nav));
49 |
--------------------------------------------------------------------------------
/src/components/NavBar/Nav/index.scss:
--------------------------------------------------------------------------------
1 | .nav_box {
2 | float: right;
3 | list-style: none;
4 | padding-right: 12px;
5 | li {
6 | padding: 0 12px;
7 | &:hover {
8 | cursor: pointer;
9 | background-color: #e6f7ff
10 | }
11 | }
12 | }
13 | .img_box {
14 | width: 26px;
15 | height: 26px;
16 | border-radius: 30px;
17 | display: inline-block;
18 | margin: 0 10px 0 0;
19 | img {
20 | width: 100%;
21 | height: 100%;
22 | }
23 | }
24 | .text {
25 | padding-left: 10px;
26 | }
--------------------------------------------------------------------------------
/src/components/NavBar/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { PropTypes } from 'prop-types';
3 | import { Link } from 'react-router-dom';
4 | import Layout from 'antd/lib/layout';
5 | import Menu from 'antd/lib/menu';
6 | import Icon from 'antd/lib/icon';
7 | import Nav from './Nav';
8 | import 'antd/lib/layout/style';
9 | import styles from './index.scss';
10 | const { Header, Sider, Content } = Layout;
11 |
12 | export default class NavBar extends Component {
13 | static propTypes = {
14 | children: PropTypes.any
15 | };
16 | state = {
17 | collapsed: false
18 | };
19 | toggle = () => {
20 | this.setState({
21 | collapsed: !this.state.collapsed
22 | });
23 | };
24 | render() {
25 | return (
26 |
27 |
28 | Logo
29 |
43 |
44 |
45 |
53 |
54 | {this.props.children}
55 |
56 |
57 |
58 | );
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/components/NavBar/index.scss:
--------------------------------------------------------------------------------
1 | .trigger {
2 | font-size: 18px;
3 | line-height: 64px;
4 | padding: 0 24px;
5 | cursor: pointer;
6 | transition: color .3s;
7 | &:hover {
8 | color: #1890ff;
9 | }
10 | }
11 |
12 | .logo {
13 | height: 32px;
14 | background: #fafafa;
15 | margin: 16px;
16 | }
17 | .layout {
18 | height: 100%;
19 | border-right: 1px solid #fafafa;
20 | }
--------------------------------------------------------------------------------
/src/containers/AppLayout.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Loadable from 'react-loadable';
3 | import { Route, Switch } from 'react-router-dom';
4 | import NotFound from '../containers/NotFound';
5 | import NavBar from '../components/NavBar';
6 | // import Article from '../components/Article';
7 |
8 | const Home = Loadable({
9 | loader: () => import('../components/CssModule' /* webpackChunkName: 'Home' */),
10 | loading: () => 加载中...
11 | });
12 |
13 | const Article = Loadable({
14 | loader: () => import('../components/Article' /* webpackChunkName: 'Article' */),
15 | loading: () => 加载中...
16 | });
17 |
18 | export class AppLayout extends React.Component {
19 | // static propTypes = {
20 | // loading: PropTypes.bool.isRequired
21 | // };
22 | render() {
23 | return (
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | );
32 | }
33 | }
34 |
35 | export default AppLayout;
36 |
--------------------------------------------------------------------------------
/src/containers/LoginForm/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import LoginFormBody from '../../components/LoginForm';
4 |
5 | export default class LoginForm extends React.Component {
6 | static propTypes = {
7 | children: PropTypes.node
8 | };
9 |
10 | static defaultProps = {
11 | children: ''
12 | };
13 |
14 | render() {
15 | return ;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/containers/NavBar/index.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import PropTypes from 'prop-types';
3 | import {Helmet} from 'react-helmet';
4 | import {observer, inject} from 'mobx-react';
5 | import styles from './index.scss';
6 |
7 | @inject('appStore')
8 | @observer
9 | export default class NavBar extends Component {
10 | static propTypes = {
11 | appStore: PropTypes.object
12 | };
13 | render() {
14 | return (
15 |
16 |
17 |
文章相关56{this.props.appStore.name}
18 |
文章相关
19 |
20 | );
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/containers/NavBar/index.less:
--------------------------------------------------------------------------------
1 | .title {
2 | font-size: 32px;
3 | }
--------------------------------------------------------------------------------
/src/containers/NavBar/index.scss:
--------------------------------------------------------------------------------
1 | .title {
2 | font-size: 32px;
3 | }
--------------------------------------------------------------------------------
/src/containers/NotFound.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default function NotFound() {
4 | return (
5 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/src/helpers/combineServer.js:
--------------------------------------------------------------------------------
1 | export default function combineServerData(allStore, data) {
2 | const keyArr = Object.keys(allStore);
3 | keyArr.map((key) => {
4 | if (allStore[key].combineServerData) {
5 | allStore[key].combineServerData(data[key]);
6 | }
7 | });
8 | }
9 |
--------------------------------------------------------------------------------
/src/helpers/history.js:
--------------------------------------------------------------------------------
1 | import { createBrowserHistory, createMemoryHistory } from 'history';
2 | // use this way to fix bug(Invariant Violation: Browser history needs a DOM)
3 | export default (typeof window !== 'undefined'
4 | ? createBrowserHistory()
5 | : createMemoryHistory());
6 |
--------------------------------------------------------------------------------
/src/imgs/BiazfanxmamNRoxxVxka.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tecode/react-mobx-ssr/e3c6d266a33742b3e81113886c755c92132946c7/src/imgs/BiazfanxmamNRoxxVxka.png
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 |
9 |
10 | react16+mobx+ssr
11 |
12 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/router.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Route, Switch } from 'react-router-dom';
3 |
4 | import AppLayout from './containers/AppLayout';
5 | import LoginForm from './containers/LoginForm';
6 |
7 | export default class Routers extends Component {
8 | render() {
9 | return (
10 |
11 |
12 |
13 |
14 | );
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/server/index.js:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import bodyParser from 'body-parser';
3 | import Loadable from 'react-loadable';
4 | import setupApiRoutes from './middlewares/api';
5 | import logger from './logger';
6 | import development from './middlewares/development';
7 | import production from './middlewares/production';
8 |
9 | process.env.NODE_ENV = process.env.NODE_ENV || 'development';
10 | const HTTP_PORT = process.env.HTTP_PORT || 3001;
11 |
12 | function onUnhandledError(err) {
13 | try {
14 | logger.error(err);
15 | } catch (e) {
16 | console.log('LOGGER ERROR:', e); //eslint-disable-line no-console
17 | console.log('APPLICATION ERROR:', err); //eslint-disable-line no-console
18 | }
19 | process.exit(1);
20 | }
21 |
22 | process.on('unhandledRejection', onUnhandledError);
23 | process.on('uncaughtException', onUnhandledError);
24 |
25 | const setupAppRoutes = process.env.NODE_ENV === 'development' ? development : production;
26 |
27 | const app = express();
28 |
29 | app.set('env', process.env.NODE_ENV);
30 | logger.info(`Application env: ${process.env.NODE_ENV}`);
31 |
32 | app.use(logger.expressMiddleware);
33 | app.use(bodyParser.json());
34 |
35 | // application routes
36 | setupApiRoutes(app);
37 | setupAppRoutes(app);
38 |
39 | if (HTTP_PORT) {
40 | Loadable.preloadAll().then(() => {
41 | app.listen(HTTP_PORT, function() {
42 | logger.info(`HTTP server is now running on http://localhost:${HTTP_PORT}`);
43 | });
44 | });
45 | } else {
46 | global.console.error(
47 | '==> 😭 OMG!!! No PORT environment variable has been specified'
48 | );
49 | }
50 |
--------------------------------------------------------------------------------
/src/server/logger.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const {homedir} = require('os');
3 | const {
4 | Logger,
5 | transports: {Console, File}
6 | } = require('winston');
7 |
8 | const LOG_FILE_NAME = '.application.log';
9 | const LOG_FILE_PATH =
10 | process.env.NODE_ENV === 'production'
11 | ? path.join(homedir(), LOG_FILE_NAME)
12 | : path.join(__dirname, '..', '..', LOG_FILE_NAME);
13 | const LOG_LEVEL = process.env.LOG_LEVEL || (process.env.NODE_ENV === 'production' ? 'verbose' : 'debug');
14 |
15 | const logger = new Logger({
16 | transports: [
17 | new Console({
18 | level: LOG_LEVEL,
19 | colorize: true,
20 | timestamp: true,
21 | prettyPrint: true
22 | }),
23 | new File({
24 | level: LOG_LEVEL,
25 | filename: LOG_FILE_PATH,
26 | handleExceptions: true,
27 | humanReadableUnhandledException: true,
28 | prettyPrint: true,
29 | tailable: true,
30 | maxsize: 10 * 1024 * 1024,
31 | maxFiles: 10,
32 | json: false
33 | })
34 | ]
35 | });
36 |
37 | logger.expressMiddleware = function expressMiddleware(req, res, next) {
38 | if (req.url.includes('__webpack') && process.env.NODE_ENV === 'development') {
39 | return next();
40 | }
41 |
42 | const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
43 | const defaultMessage = `${ip} - ${req.method} ${req.url}`;
44 | const startTimestemp = Date.now();
45 | const waitingTimePrintInterval = 5000;
46 |
47 | let waitingTime = 0;
48 | const intervalId = setInterval(() => {
49 | waitingTime += waitingTimePrintInterval;
50 | logger.verbose(`${defaultMessage} - wait for ${waitingTime / 1000}s...`);
51 | }, waitingTimePrintInterval);
52 |
53 | const printExecutionTime = (statusCode = '') => {
54 | const message = `${defaultMessage} - ${statusCode} - ${(Date.now() - startTimestemp) / 1000}s`;
55 | if (res.statusCode < 400) {
56 | logger.info(message);
57 | } else {
58 | logger.warn(message);
59 | }
60 | clearInterval(intervalId);
61 | };
62 |
63 | req.on('end', () => printExecutionTime(res.statusCode));
64 | req.on('close', () => printExecutionTime('[closed by user]'));
65 |
66 | return next();
67 | };
68 |
69 | logger.info(`Application logs file: ${LOG_FILE_PATH}`);
70 |
71 | module.exports = logger;
72 |
--------------------------------------------------------------------------------
/src/server/middlewares/api.js:
--------------------------------------------------------------------------------
1 | // API
2 |
3 | const servers = [{id: 1, name: 'a'}, {id: 2, name: 'b'}, {id: 3, name: 'c'}];
4 |
5 | export default function(app) {
6 | app.get('/api/stats', (req, res) => {
7 | setTimeout(() => {
8 | res.json({
9 | // error: 'server error message',
10 | status: 'online',
11 | servers
12 | });
13 | }, 3000);
14 | });
15 |
16 | app.post('/api/servers', (req, res) => {
17 | if (!req.body.name) {
18 | return res.json({
19 | error: 'cannot add server with empty name'
20 | });
21 | }
22 | return setTimeout(() => {
23 | servers.push({
24 | id: servers[servers.length - 1].id + 1,
25 | name: req.body.name
26 | });
27 | res.json({
28 | success: true
29 | });
30 | }, 3000);
31 | });
32 | }
33 |
--------------------------------------------------------------------------------
/src/server/middlewares/development.js:
--------------------------------------------------------------------------------
1 | import {resolve} from 'path';
2 | import webpack from 'webpack';
3 | import webpackDevMiddleware from 'webpack-dev-middleware';
4 | import webpackHotMiddleware from 'webpack-hot-middleware';
5 | import logger from '../logger';
6 | import webpackConfig from '../../../config/webpack.config.dev';
7 | import renderHtml from './renderHtml';
8 |
9 | const compiler = webpack(webpackConfig);
10 |
11 | export default function(app) {
12 | app.use(
13 | webpackDevMiddleware(compiler, {
14 | logger,
15 | publicPath: webpackConfig.output.publicPath,
16 | stats: {
17 | colors: true
18 | }
19 | })
20 | );
21 |
22 | app.use(webpackHotMiddleware(compiler));
23 |
24 | // all other requests be handled by UI itself
25 | app.get('*', (req, res) => {
26 | res.status('200');
27 | res.send(renderHtml(resolve(__dirname, '..', '..', '..', 'build-dev', 'client.html'), req));
28 | });
29 | }
30 |
--------------------------------------------------------------------------------
/src/server/middlewares/production.js:
--------------------------------------------------------------------------------
1 | import {resolve} from 'path';
2 | import express from 'express';
3 | import compression from 'compression';
4 | import renderHtml from './renderHtml';
5 |
6 | const clientBuildPath = resolve(__dirname, '..', '..', '..', 'build');
7 |
8 | export default function(app) {
9 | app.use(compression());
10 | app.use('/', express.static(clientBuildPath));
11 |
12 | // all other requests be handled by UI itself
13 | app.get('*', (req, res) => {
14 | res.status('200');
15 | res.send(renderHtml(resolve(clientBuildPath, 'server.html'), req));
16 | });
17 | }
18 |
--------------------------------------------------------------------------------
/src/server/middlewares/renderHtml.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import fs from 'fs';
3 | import { Helmet } from 'react-helmet';
4 | import cheerio from 'cheerio';
5 | import { renderToString } from 'react-dom/server';
6 | import { StaticRouter } from 'react-router';
7 | import { Provider } from 'mobx-react';
8 | import App from '../../router';
9 | import allStore from '../../store';
10 | import { toJS } from 'mobx';
11 |
12 | export default function (path, req) {
13 | allStore.miniStore = { name: 'PPPPP' };
14 | const context = {};
15 | const componentHTML = (
16 |
17 |
18 |
19 |
20 |
21 | );
22 | const prepareStore = (store) => {
23 | const keyArr = Object.keys(allStore);
24 | const output = {};
25 | keyArr.forEach((key) => {
26 | output[key] = toJS(store[key]);
27 | });
28 | return output;
29 | };
30 | const helmet = Helmet.renderStatic();
31 | const HTML_TEMPLATE = fs.readFileSync(path).toString();
32 | const $template = cheerio.load(HTML_TEMPLATE, { decodeEntities: false });
33 | $template('head').append(helmet.title.toString() + helmet.meta.toString() + helmet.link.toString());
34 | // console.log(renderToString(), '----------------------------appString')
35 | $template('#app').html(`${renderToString(componentHTML)}`);
36 | $template('#app').after(``);
37 | return $template.html();
38 | }
39 |
--------------------------------------------------------------------------------
/src/store/appStore.js:
--------------------------------------------------------------------------------
1 | import {observable, action} from 'mobx';
2 |
3 | class AppSore {
4 | @observable name = 'solo8969';
5 | @observable day = '2096';
6 | @action.bound
7 | log() {
8 | console.log('mobx');
9 | }
10 | }
11 |
12 | export default new AppSore();
13 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import appStore from './appStore';
2 |
3 | const allStore = () => ({
4 | homeStore: {name: 'bob', age: '12'},
5 | appStore
6 | });
7 |
8 | export default allStore();
9 |
--------------------------------------------------------------------------------
/src/style.scss:
--------------------------------------------------------------------------------
1 | html, body {
2 | background-color: white;
3 | min-width: 700px;
4 | font-family: 'Comfortaa';
5 | }
6 |
--------------------------------------------------------------------------------