diff --git a/example/ssr-with-mobx/.gitignore b/example/ssr-with-mobx/.gitignore new file mode 100644 index 00000000..bde43781 --- /dev/null +++ b/example/ssr-with-mobx/.gitignore @@ -0,0 +1,71 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# next.js build output +.next + +.DS_Store + +.vscode + +run/ + +dist/ + +package-lock.json \ No newline at end of file diff --git a/example/ssr-with-mobx/README.md b/example/ssr-with-mobx/README.md new file mode 100755 index 00000000..98ee5268 --- /dev/null +++ b/example/ssr-with-mobx/README.md @@ -0,0 +1,33 @@ +# Egg + React + SSR应用骨架 + +详细用法实现请查看[官方文档](http://ykfe.net) + +# Getting Start + +这里我们提供了一个脚手架来方便你创建项目 + +``` +$ npm install yk-cli -g +$ ykcli init +$ cd +$ npm i +$ npm start +$ open http://localhost:7001 +``` + +# 功能/特性 + +- [x] 基于cra脚手架开发,由cra开发的React App可无缝迁移,如果你熟悉cra的配置,上手成本几乎为0 +- [x] 小而美,相比于beidou,next.js这样的高度封装方案,我们的实现原理和开发模式一目了然 +- [x] 同时支持SSR以及CSR两种开发模式,本地开发环境以及线上环境皆可无缝切换两种渲染模式 +- [x] 统一前端路由与服务端路由,无需重复编写路由文件配置 +- [x] 支持切换路由时自动获取数据 +- [x] 支持本地开发HMR +- [x] 稳定性经过线上大规模应用验证,可提供性能优化方案 +- [x] 支持tree shaking以及打包去重依赖,使得打包的bundle非常小,为同样复杂度的next.js项目的0.4倍 +- [x] 支持csr/ssr自定义layout,无需通过path来手动区分 +- [ ] 配套[TypeScript](https://github.com/ykfe/egg-react-ssr-typescript)版本的实现 +- [ ] 配套serverless版本的实现 + + + diff --git a/example/ssr-with-mobx/app.js b/example/ssr-with-mobx/app.js new file mode 100755 index 00000000..c9a76360 --- /dev/null +++ b/example/ssr-with-mobx/app.js @@ -0,0 +1,4 @@ + +module.exports = app => { + +} diff --git a/example/ssr-with-mobx/app/controller/page.js b/example/ssr-with-mobx/app/controller/page.js new file mode 100644 index 00000000..b1ca1045 --- /dev/null +++ b/example/ssr-with-mobx/app/controller/page.js @@ -0,0 +1,20 @@ + +const Controller = require('egg').Controller +const { renderToStream } = require('ykfe-utils') + +class PageController extends Controller { + async index () { + const { ctx } = this + try { + // Page为webpack打包的chunkName,项目默认的entry为Page + ctx.type = 'text/html' + ctx.status = 200 + const stream = await renderToStream(ctx, 'Page', ctx.app.config) + ctx.body = stream + } catch (error) { + ctx.logger.error(`Page Controller renderToStream Error ${error}`) + } + } +} + +module.exports = PageController diff --git a/example/ssr-with-mobx/app/router.js b/example/ssr-with-mobx/app/router.js new file mode 100755 index 00000000..c8e325f1 --- /dev/null +++ b/example/ssr-with-mobx/app/router.js @@ -0,0 +1,8 @@ +'use strict' + +module.exports = app => { + const { router, controller } = app + app.config.routes.map(route => { + router.get(`${route.path}`, controller[route.controller][route.handler]) + }) +} diff --git a/example/ssr-with-mobx/build/env.js b/example/ssr-with-mobx/build/env.js new file mode 100755 index 00000000..afe5d437 --- /dev/null +++ b/example/ssr-with-mobx/build/env.js @@ -0,0 +1,93 @@ +'use strict' + +const fs = require('fs') +const path = require('path') +const paths = require('./paths') + +// Make sure that including paths.js after env.js will read .env variables. +delete require.cache[require.resolve('./paths')] + +const NODE_ENV = process.env.NODE_ENV +if (!NODE_ENV) { + throw new Error( + 'The NODE_ENV environment variable is required but was not specified.' + ) +} + +// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use +var dotenvFiles = [ + `${paths.dotenv}.${NODE_ENV}.local`, + `${paths.dotenv}.${NODE_ENV}`, + // Don't include `.env.local` for `test` environment + // since normally you expect tests to produce the same + // results for everyone + NODE_ENV !== 'test' && `${paths.dotenv}.local`, + paths.dotenv +].filter(Boolean) + +// Load environment variables from .env* files. Suppress warnings using silent +// if this file is missing. dotenv will never modify any environment variables +// that have already been set. Variable expansion is supported in .env files. +// https://github.com/motdotla/dotenv +// https://github.com/motdotla/dotenv-expand +dotenvFiles.forEach(dotenvFile => { + if (fs.existsSync(dotenvFile)) { + require('dotenv-expand')( + require('dotenv').config({ + path: dotenvFile + }) + ) + } +}) + +// We support resolving modules according to `NODE_PATH`. +// This lets you use absolute paths in imports inside large monorepos: +// https://github.com/facebook/create-react-app/issues/253. +// It works similar to `NODE_PATH` in Node itself: +// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders +// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. +// Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. +// https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421 +// We also resolve them to make sure all tools using them work consistently. +const appDirectory = fs.realpathSync(process.cwd()) +process.env.NODE_PATH = (process.env.NODE_PATH || '') + .split(path.delimiter) + .filter(folder => folder && !path.isAbsolute(folder)) + .map(folder => path.resolve(appDirectory, folder)) + .join(path.delimiter) + +// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be +// injected into the application via DefinePlugin in Webpack configuration. +const REACT_APP = /^REACT_APP_/i + +function getClientEnvironment (publicUrl) { + const raw = Object.keys(process.env) + .filter(key => REACT_APP.test(key)) + .reduce( + (env, key) => { + env[key] = process.env[key] + return env + }, + { + // Useful for determining whether we’re running in production mode. + // Most importantly, it switches React into the correct mode. + NODE_ENV: process.env.NODE_ENV || 'development', + // Useful for resolving the correct path to static assets in `public`. + // For example, . + // This should only be used as an escape hatch. Normally you would put + // images into the `src` and `import` them in code to get their paths. + PUBLIC_URL: publicUrl + } + ) + // Stringify all values so we can feed into Webpack DefinePlugin + const stringified = { + 'process.env': Object.keys(raw).reduce((env, key) => { + env[key] = JSON.stringify(raw[key]) + return env + }, {}) + } + + return { raw, stringified } +} + +module.exports = getClientEnvironment diff --git a/example/ssr-with-mobx/build/jest/cssTransform.js b/example/ssr-with-mobx/build/jest/cssTransform.js new file mode 100755 index 00000000..10a0a4c1 --- /dev/null +++ b/example/ssr-with-mobx/build/jest/cssTransform.js @@ -0,0 +1,14 @@ +'use strict' + +// This is a custom Jest transformer turning style imports into empty objects. +// http://facebook.github.io/jest/docs/en/webpack.html + +module.exports = { + process () { + return 'module.exports = {};' + }, + getCacheKey () { + // The output is always the same. + return 'cssTransform' + } +} diff --git a/example/ssr-with-mobx/build/jest/fileTransform.js b/example/ssr-with-mobx/build/jest/fileTransform.js new file mode 100755 index 00000000..c0d0f075 --- /dev/null +++ b/example/ssr-with-mobx/build/jest/fileTransform.js @@ -0,0 +1,30 @@ +'use strict' + +const path = require('path') + +// This is a custom Jest transformer turning file imports into filenames. +// http://facebook.github.io/jest/docs/en/webpack.html + +module.exports = { + process (src, filename) { + const assetFilename = JSON.stringify(path.basename(filename)) + + if (filename.match(/\.svg$/)) { + return `module.exports = { + __esModule: true, + default: ${assetFilename}, + ReactComponent: (props) => ({ + $$typeof: Symbol.for('react.element'), + type: 'svg', + ref: null, + key: null, + props: Object.assign({}, props, { + children: ${assetFilename} + }) + }), + };` + } + + return `module.exports = ${assetFilename};` + } +} diff --git a/example/ssr-with-mobx/build/paths.js b/example/ssr-with-mobx/build/paths.js new file mode 100755 index 00000000..5f5ff07d --- /dev/null +++ b/example/ssr-with-mobx/build/paths.js @@ -0,0 +1,71 @@ +'use strict' + +const path = require('path') +const fs = require('fs') +const url = require('url') + +// Make sure any symlinks in the project folder are resolved: +// https://github.com/facebook/create-react-app/issues/637 +const appDirectory = fs.realpathSync(process.cwd()) +const resolveApp = relativePath => path.resolve(appDirectory, relativePath) + +const envPublicUrl = process.env.PUBLIC_URL + +function ensureSlash (inputPath, needsSlash) { + const hasSlash = inputPath.endsWith('/') + if (hasSlash && !needsSlash) { + return inputPath.substr(0, inputPath.length - 1) + } else if (!hasSlash && needsSlash) { + return `${inputPath}/` + } else { + return inputPath + } +} + +const getPublicUrl = appPackageJson => + envPublicUrl || require(appPackageJson).homepage + +// We use `PUBLIC_URL` environment variable or "homepage" field to infer +// "public path" at which the app is served. +// Webpack needs to know it to put the right `, + ``, + `` + ]), // 客户端需要加载的静态资源文件表 + serverJs: (chunkName) => resolvePath(`dist/${chunkName}.server.js`) +} diff --git a/example/ssr-with-mobx/config/config.local.js b/example/ssr-with-mobx/config/config.local.js new file mode 100755 index 00000000..bdd5fe82 --- /dev/null +++ b/example/ssr-with-mobx/config/config.local.js @@ -0,0 +1,7 @@ + +module.exports = { + proxy: { + host: 'http://127.0.0.1:8000', // 本地开发的时候代理前端打包出来的资源地址 + match: /(\/static)|(\/sockjs-node)|(\/__webpack_dev_server__)|hot-update/ + } +} diff --git a/example/ssr-with-mobx/config/config.prod.js b/example/ssr-with-mobx/config/config.prod.js new file mode 100755 index 00000000..7acaac2b --- /dev/null +++ b/example/ssr-with-mobx/config/config.prod.js @@ -0,0 +1,3 @@ +module.exports = { + +} diff --git a/example/ssr-with-mobx/config/plugin.js b/example/ssr-with-mobx/config/plugin.js new file mode 100755 index 00000000..7acaac2b --- /dev/null +++ b/example/ssr-with-mobx/config/plugin.js @@ -0,0 +1,3 @@ +module.exports = { + +} diff --git a/example/ssr-with-mobx/config/plugin.local.js b/example/ssr-with-mobx/config/plugin.local.js new file mode 100644 index 00000000..48fc6ed6 --- /dev/null +++ b/example/ssr-with-mobx/config/plugin.local.js @@ -0,0 +1,6 @@ +module.exports = { + proxy: { + package: 'egg-proxy', + enable: true + } +} diff --git a/example/ssr-with-mobx/package.json b/example/ssr-with-mobx/package.json new file mode 100755 index 00000000..2fcd57e3 --- /dev/null +++ b/example/ssr-with-mobx/package.json @@ -0,0 +1,75 @@ +{ + "name": "egg-ssr", + "version": "1.1.3", + "dependencies": { + "egg": "^2.21.0", + "egg-bin": "^4.13.1", + "egg-proxy": "^1.1.0", + "egg-scripts": "^2.11.0", + "egg-static": "^2.2.0", + "koa-router": "^7.4.0", + "mobx-react": "^6.1.1", + "mobx-state-tree": "^3.14.0", + "react": "^16.8.3", + "react-dev-utils": "^8.0.0", + "react-dom": "^16.8.3", + "react-router-dom": "^5.0.0", + "ykfe-utils": "^1.1.0" + }, + "scripts": { + "debug": "egg-bin debug", + "start": "rimraf dist && concurrently \"npm run ssr\" \" npm run csr \"", + "ssr": "concurrently \"egg-bin dev\" \"cross-env NODE_ENV=development webpack --watch --config ./build/webpack.config.server.js\"", + "csr": "cross-env NODE_ENV=development webpack-dev-server --port 8000 --hot --client-log-level error --config ./build/webpack.config.client.js", + "prod": "npm run build && eggctl start", + "build:server": "cross-env NODE_ENV=production webpack --config build/webpack.config.server.js", + "build": "rimraf dist && cross-env NODE_ENV=production webpack --config build/webpack.config.client.js && npm run build:server", + "analyze": "cross-env NODE_ENV=production npm_config_report=true npm run build" + }, + "browserslist": [ + ">0.2%", + "not dead", + "not ie <= 11", + "not op_mini all" + ], + "resolutions": { + "browserslist": "4.6.3", + "caniuse-lite": "1.0.30000974" + }, + "devDependencies": { + "@babel/core": "^7.4.4", + "@babel/plugin-proposal-class-properties": "^7.4.4", + "@babel/plugin-proposal-decorators": "^7.4.4", + "@babel/preset-react": "^7.0.0", + "@babel/register": "^7.0.0", + "@svgr/webpack": "2.4.1", + "babel-loader": "8.0.4", + "browserslist": "^4.6.3", + "caniuse-lite": "1.0.30000974", + "concurrently": "^4.1.0", + "cross-env": "^5.2.0", + "css-hot-loader": "^1.4.3", + "css-loader": "1.0.0", + "css-modules-require-hook": "^4.2.3", + "file-loader": "2.0.0", + "html-webpack-plugin": "^3.2.0", + "less": "^3.9.0", + "less-loader": "^4.1.0", + "mini-css-extract-plugin": "^0.5.0", + "optimize-css-assets-webpack-plugin": "5.0.1", + "postcss-flexbugs-fixes": "4.1.0", + "postcss-loader": "3.0.0", + "postcss-preset-env": "^6.0.5", + "postcss-safe-parser": "4.0.1", + "rimraf": "^2.6.3", + "terser-webpack-plugin": "^1.2.0", + "url-loader": "1.1.1", + "webpack": "4.19.1", + "webpack-bundle-analyzer": "^3.0.3", + "webpack-cli": "^3.3.3", + "webpack-dev-server": "3.3.1", + "webpack-manifest-plugin": "^2.0.4", + "webpack-merge": "^4.1.4", + "webpack-node-externals": "^1.7.2" + } +} diff --git a/example/ssr-with-mobx/web/assets/common.less b/example/ssr-with-mobx/web/assets/common.less new file mode 100755 index 00000000..b78da2a3 --- /dev/null +++ b/example/ssr-with-mobx/web/assets/common.less @@ -0,0 +1,13 @@ +* { + margin: 0; + padding: 0; +} + +a { + color: #fff; + text-decoration: none; +} + +ul { + list-style: none; +} \ No newline at end of file diff --git a/example/ssr-with-mobx/web/entry.js b/example/ssr-with-mobx/web/entry.js new file mode 100644 index 00000000..6f7d42ad --- /dev/null +++ b/example/ssr-with-mobx/web/entry.js @@ -0,0 +1,61 @@ +import React from 'react' +import ReactDOM from 'react-dom' +import { BrowserRouter, StaticRouter, Route } from 'react-router-dom' +import defaultLayout from '@/layout' +import { getWrappedComponent, getComponent } from 'ykfe-utils' +import { routes as Routes } from '../config/config.default' +import { Provider, useStaticRendering } from 'mobx-react' +import initStore from './store' + +const clientRender = async () => { + const store = initStore({ + __isBrowser__: __isBrowser__, + initialState: window.__INITIAL_DATA__ || {} + }) + // 客户端渲染||hydrate + ReactDOM[window.__USE_SSR__ ? 'hydrate' : 'render']( + + + { + // 使用高阶组件getWrappedComponent使得csr首次进入页面以及csr/ssr切换路由时调用getInitialProps + Routes.map(({ path, exact, Component }, key) => { + const ActiveComponent = Component() + const Layout = ActiveComponent.Layout || defaultLayout + return { + const WrappedComponent = getWrappedComponent(ActiveComponent) + return + }} /> + }) + } + + + + , document.getElementById('app')) + + if (process.env.NODE_ENV === 'development' && module.hot) { + module.hot.accept() + } +} + +const serverRender = async (ctx) => { + useStaticRendering(true) + const store = initStore({ + __isBrowser__: __isBrowser__ + }) + ctx.store = store + // 服务端渲染 根据ctx.path获取请求的具体组件,调用getInitialProps并渲染 + const ActiveComponent = getComponent(Routes, ctx.path)() + const serverData = ActiveComponent.getInitialProps ? await ActiveComponent.getInitialProps(ctx) : {} + const Layout = ActiveComponent.Layout || defaultLayout + ctx.serverData = store + + return + + + + + + +} + +export default __isBrowser__ ? clientRender() : serverRender diff --git a/example/ssr-with-mobx/web/index.html b/example/ssr-with-mobx/web/index.html new file mode 100644 index 00000000..9693b55d --- /dev/null +++ b/example/ssr-with-mobx/web/index.html @@ -0,0 +1,21 @@ + + + + + + + + React App + + + + + +
+ +
+ + + + + \ No newline at end of file diff --git a/example/ssr-with-mobx/web/layout/index.js b/example/ssr-with-mobx/web/layout/index.js new file mode 100644 index 00000000..db7e6877 --- /dev/null +++ b/example/ssr-with-mobx/web/layout/index.js @@ -0,0 +1,12 @@ + +import React from 'react' +import '../assets/common.less' +import './index.less' +import { Link } from 'react-router-dom' + +const Layout = (props) => { + return ( +

Egg + React + SSR
by ykfe

{props.children}
) +} + +export default Layout diff --git a/example/ssr-with-mobx/web/layout/index.less b/example/ssr-with-mobx/web/layout/index.less new file mode 100644 index 00000000..8a95da09 --- /dev/null +++ b/example/ssr-with-mobx/web/layout/index.less @@ -0,0 +1,22 @@ +.normal { + font-family: Georgia, sans-serif; + text-align: center; +} +body { + background-color: #f2f3f5; +} +.title { + position: relative; + display: flex; + align-items: center; + justify-content: center; + height: 55px; + background-color: darkslateblue; + color: #fff; +} +.author { + position: absolute; + font-size: 15px; + bottom: 10px; + right: 100px; +} diff --git a/example/ssr-with-mobx/web/page/index/index.js b/example/ssr-with-mobx/web/page/index/index.js new file mode 100644 index 00000000..deaa786a --- /dev/null +++ b/example/ssr-with-mobx/web/page/index/index.js @@ -0,0 +1,32 @@ +import React from 'react' +import { Link } from 'react-router-dom' +import './index.less' +import { inject, observer } from 'mobx-react' + +function Page (props) { + return ( +
+
+
    + { + props.news && props.news.map((item, index) => ( +
  • +
    文章标题: {item.title}
    +
    点击查看详情
    +
  • + )) + } +
+
+ ) +} + +Page.getInitialProps = async ({ store }) => { + await store.pageStore.getData() +} + +const mapStateToProps = ({ store }) => ({ + news: store.pageStore.news +}) + +export default inject(mapStateToProps)(observer(Page)) diff --git a/example/ssr-with-mobx/web/page/index/index.less b/example/ssr-with-mobx/web/page/index/index.less new file mode 100644 index 00000000..d3104c00 --- /dev/null +++ b/example/ssr-with-mobx/web/page/index/index.less @@ -0,0 +1,19 @@ +.list { + background-color: #fff; + width: 1000px; + margin: 100px auto; + li { + display: flex; + justify-content: space-between; + background-color: #fff; + padding: 20px 30px 20px 80px; + border-bottom: 1px solid #eee; + position: relative; + line-height: 20px; + .toDetail a{ + font-size: 14px; + color: #888 + } + } +} + diff --git a/example/ssr-with-mobx/web/page/news/index.js b/example/ssr-with-mobx/web/page/news/index.js new file mode 100644 index 00000000..4173fb74 --- /dev/null +++ b/example/ssr-with-mobx/web/page/news/index.js @@ -0,0 +1,26 @@ +import React from 'react' +import Layout from './layout' +import './index.less' +import { inject, observer } from 'mobx-react' + +function News (props) { + return ( +
+ 文章详情: {props.detail} +
+ ) +} + +// 自定义Layout +News.Layout = Layout + +News.getInitialProps = async (ctx) => { + const newsId = __isBrowser__ ? ctx.match.params.id : ctx.params.id + await ctx.store.newsStore.getData(newsId) +} + +const mapStateToProps = ({ store }) => ({ + detail: store.newsStore.detail +}) + +export default inject(mapStateToProps)(observer(News)) diff --git a/example/ssr-with-mobx/web/page/news/index.less b/example/ssr-with-mobx/web/page/news/index.less new file mode 100644 index 00000000..d77837fa --- /dev/null +++ b/example/ssr-with-mobx/web/page/news/index.less @@ -0,0 +1,9 @@ +.news-container { + width: 1000px; + background-color: #fff; + margin: 100px auto; + text-align: left; + line-height: 30px; + letter-spacing: 1px; + padding: 10px; +} \ No newline at end of file diff --git a/example/ssr-with-mobx/web/page/news/layout.js b/example/ssr-with-mobx/web/page/news/layout.js new file mode 100644 index 00000000..563ab05a --- /dev/null +++ b/example/ssr-with-mobx/web/page/news/layout.js @@ -0,0 +1,12 @@ + +import React from 'react' +import '@/assets/common.less' +import './layout.less' +import { Link } from 'react-router-dom' + +const Layout = (props) => { + return ( +

News Page
by ykfe

{props.children}
) +} + +export default Layout diff --git a/example/ssr-with-mobx/web/page/news/layout.less b/example/ssr-with-mobx/web/page/news/layout.less new file mode 100644 index 00000000..bb423e66 --- /dev/null +++ b/example/ssr-with-mobx/web/page/news/layout.less @@ -0,0 +1,22 @@ +.normal { + font-family: Georgia, sans-serif; + text-align: center; +} +body { + background-color: #f2f3f5; +} +.title { + position: relative; + display: flex; + align-items: center; + justify-content: center; + height: 55px; + background-color: darkslateblue; + color: #fff; +} +.author { + position: absolute; + font-size: 15px; + bottom: 10px; + right: 100px; +} diff --git a/example/ssr-with-mobx/web/store/index.js b/example/ssr-with-mobx/web/store/index.js new file mode 100644 index 00000000..7a642056 --- /dev/null +++ b/example/ssr-with-mobx/web/store/index.js @@ -0,0 +1,23 @@ +import PageStore from './page' +import NewsStore from './news' + +class Store { + constructor (options) { + const { initialState } = options + this.pageStore = new PageStore(initialState) + this.newsStore = new NewsStore(initialState) + } +} + +let store = null + +function initializeStore (options) { + const isServer = !options.__isBrowser__ + + if (isServer || store === null) { + store = new Store(options) + } + + return store +} +export default initializeStore diff --git a/example/ssr-with-mobx/web/store/news.js b/example/ssr-with-mobx/web/store/news.js new file mode 100644 index 00000000..a472ac7d --- /dev/null +++ b/example/ssr-with-mobx/web/store/news.js @@ -0,0 +1,30 @@ +import { action, observable, runInAction } from 'mobx' + +const mockData = { + '1': `Racket-on-Chez continues to improve. Snapshot builds are currently available at pre.racket-lang.org, and we expect that Racket-on-Chez will be included as a download option in the next release.`, + '2': `This means anyone with more than three devices connected doesn't have to worry right this instant. That will change, however, when it comes time to replace one of your current devices or if you add another device to your collection. At that point, you will have to make a decision.`, + '3': `World's most mysterious text is finally cracked: Bristol academic deciphers lost language of 600-year-old Voynich manuscript to reveal astrological sex tips, herbal remedies and other pagan beliefs`, + '4': `After a successful test in Mexico City, fast-food chain Burger King will begin delivering food to drivers caught in traffic in Los Angeles in what they have dubbed The Traffic Jam Whopper.`, + '5': `Product advertisement and promotion on YouTube is a function of the dedicated audience (or influence) of the individual (influencer) anchoring the advertising or promotion.` +} +const getData = async (id) => { + return Promise.resolve(mockData[id]) +} + +class NewsStore { + @observable detail = '' + + constructor (state) { + this.detail = (state && state.newsStore.detail) || '' + } + + @action + async getData (id) { + const detail = await getData(id) + runInAction(() => { + this.detail = detail + }) + } +} + +export default NewsStore diff --git a/example/ssr-with-mobx/web/store/page.js b/example/ssr-with-mobx/web/store/page.js new file mode 100644 index 00000000..573c6d8c --- /dev/null +++ b/example/ssr-with-mobx/web/store/page.js @@ -0,0 +1,40 @@ +import { action, observable, runInAction } from 'mobx' + +const getData = async () => { + return Promise.resolve([ + { + id: '1', + title: 'Racket v7.3 Release Notes' + }, + { + id: '2', + title: 'Free Dropbox Accounts Now Only Sync to Three Devices' + }, + { id: '3', + title: 'Voynich Manuscript Decoded by Bristol Academic' + }, + { id: '4', + title: 'Burger King to Deliver Whoppers to LA Drivers Stuck in Traffic' + }, + { id: '5', + title: 'How much do YouTube celebrities charge to advertise your product? ' + } + ]) +} +class PageStore { + @observable news = [] + + constructor (state) { + this.news = (state && state.pageStore.news) || [] + } + + @action + async getData () { + const news = await getData() + runInAction(() => { + this.news = news + }) + } +} + +export default PageStore diff --git a/package.json b/package.json index 562ef9d2..01146f89 100644 --- a/package.json +++ b/package.json @@ -46,10 +46,15 @@ }, "homepage": "https://github.com/ykfe/egg-react-ssr#readme", "devDependencies": { + "@babel/plugin-proposal-class-properties": "^7.4.4", "babel-eslint": "^10.0.2", "lerna": "^3.8.0", "pre-commit": "^1.2.2", "standard": "^12.0.1", "vuepress": "^0.14.8" + }, + "__npminstall_done": false, + "dependencies": { + "mobx": "^5.10.1" } }