From 4078be3ec68c78bd24b77025e7ffc37c6a4c3638 Mon Sep 17 00:00:00 2001 From: Diablohu Date: Tue, 11 Jun 2019 19:37:25 +0800 Subject: [PATCH] start working on #104 --- CHANGELOG.md | 2 +- .../koot-webpack/factory-config/common.js | 10 +- packages/koot/React/client-session-store.js | 74 +++++++ packages/koot/React/redux.js | 6 +- packages/koot/ReactApp/client/index.js | 181 ++++++++---------- packages/koot/ReactSPA/client/run.js | 135 ++++++------- packages/koot/defaults/koot-config.js | 5 +- packages/koot/package.json | 2 +- packages/koot/utils/init-node-env.js | 23 +-- test/projects/simple/package.json | 2 +- test/projects/standard/koot.config.js | 1 + test/projects/standard/package.json | 2 +- test/projects/standard/src/components/app.jsx | 31 +-- 13 files changed, 262 insertions(+), 212 deletions(-) create mode 100644 packages/koot/React/client-session-store.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f3ad72a..00bebd4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ - `url-loader` -> _2.0.0_ - minor - `mini-css-extract-plugin` -> _0.7.0_ - - `react-hot-loader` -> _4.9.0_ + - `react-hot-loader` -> _4.11.0_ - `webpack` -> _4.33.0_ - `webpack-dev-server` -> _3.7.1_ - patch diff --git a/packages/koot-webpack/factory-config/common.js b/packages/koot-webpack/factory-config/common.js index 9fb58e8d..066ac7c9 100644 --- a/packages/koot-webpack/factory-config/common.js +++ b/packages/koot-webpack/factory-config/common.js @@ -67,7 +67,8 @@ const factory = async ({ const plugins = async ( env, stage, - defines = {} /*, remainingKootBuildConfig = {}*/ + defines = {}, + remainingKootBuildConfig = {} ) => { const _defaultDefines = {}; Object.keys(defaultDefines).forEach(key => { @@ -107,6 +108,7 @@ const plugins = async ( // } } + // 打入环境变量 const envsToDefine = [ 'KOOT_VERSION', 'KOOT_PROJECT_NAME', @@ -132,6 +134,12 @@ const plugins = async ( if (process.env.KOOT_CLIENT_BUNDLE_SUBFOLDER) { envsToDefine.push('KOOT_CLIENT_BUNDLE_SUBFOLDER'); } + if (typeof remainingKootBuildConfig.sessionStore !== 'object') { + process.env.KOOT_SESSION_STORE = JSON.stringify( + remainingKootBuildConfig.sessionStore + ); + envsToDefine.push('KOOT_SESSION_STORE'); + } JSON.parse(process.env.KOOT_CUSTOM_ENV_KEYS).forEach(key => { if (typeof process.env[key] !== 'undefined') envsToDefine.push(key); diff --git a/packages/koot/React/client-session-store.js b/packages/koot/React/client-session-store.js new file mode 100644 index 00000000..043a0b13 --- /dev/null +++ b/packages/koot/React/client-session-store.js @@ -0,0 +1,74 @@ +import { store as Store } from '../'; + +const sessionStorageKey = '__KOOT_SESSION_STORE__'; +const configSessionStore = JSON.parse(process.env.KOOT_SESSION_STORE); + +/** @type {String[]} 这些项目不予同步 */ +const itemsBlacklist = ['localeId', 'realtimeLocation', 'routing', 'server']; + +/** @type {Boolean} 当前是否可以/允许使用 sessionStore */ +const able = (() => { + if (__SERVER__) return false; + if (!window.sessionStorage) return false; + if (!configSessionStore) return false; + + if (configSessionStore === true) return true; + if (configSessionStore === 'all') return true; + + return Boolean( + typeof configSessionStore === 'object' && + !Array.isArray(configSessionStore) + ); +})(); + +/** + * 保存当前 state 到 sessionStorage + * @void + */ +export const save = () => { + if (!able) return; + + /** @type {Object} 排除掉黑名单内的项目后的 state 对象 */ + const state = itemsBlacklist.reduce((state, item) => { + const { [item]: _, ...rest } = state; + return rest; + }, Store.getState()); + + sessionStorage.setItem( + sessionStorageKey, + configSessionStore === true || configSessionStore === 'all' + ? JSON.stringify(state) + : (state => { + const result = {}; + console.log({ configSessionStore, state, result }); + return JSON.stringify(result); + })(state) + ); + + return; +}; + +/** + * 向 window.onunload 添加事件:保存 state + * @void + */ +export const addEventHandlerOnPageUnload = () => { + if (!able) return; + window.addEventListener('unload', save); + return; +}; + +/** + * 从 sessionStorage 中读取 state + * @returns {Object} 存储的 state + */ +export const load = () => { + if (!able) return {}; + + if (configSessionStore === true || configSessionStore === 'all') + return JSON.parse(sessionStorage.getItem(sessionStorageKey) || '{}'); + + const result = {}; + + return result; +}; diff --git a/packages/koot/React/redux.js b/packages/koot/React/redux.js index 88d6e1bf..a586b8e9 100644 --- a/packages/koot/React/redux.js +++ b/packages/koot/React/redux.js @@ -18,6 +18,7 @@ import { import isI18nEnabled from '../i18n/is-enabled'; // import history from "__KOOT_CLIENT_REQUIRE_HISTORY__" import history from './history'; +import { load as loadSessionStore } from './client-session-store'; // const getHistory = () => { // if (__SPA__) { // return require('react-router/lib/hashHistory') @@ -59,7 +60,10 @@ if (isI18nEnabled()) { * @type {Object} */ export const initialState = (() => { - if (__CLIENT__) return window.__REDUX_STATE__; + if (__CLIENT__) { + console.log('sessionStore', loadSessionStore()); + return window.__REDUX_STATE__; + } if (__SERVER__) return {}; })(); diff --git a/packages/koot/ReactApp/client/index.js b/packages/koot/ReactApp/client/index.js index 9272ccfc..942dcf1b 100644 --- a/packages/koot/ReactApp/client/index.js +++ b/packages/koot/ReactApp/client/index.js @@ -1,140 +1,120 @@ +import * as fullConfig from '__KOOT_PROJECT_CONFIG_FULL_PATHNAME__'; + +import React from 'react'; +import { hydrate } from 'react-dom'; +// import { syncHistoryWithStore } from 'react-router-redux' +import routerMatch from 'react-router/lib/match'; + +// ---------------------------------------------------------------------------- + import { localeId as LocaleId, store as Store, history as History -} from '../../index' - +} from '../../index'; // ---------------------------------------------------------------------------- +import validateRouterConfig from '../../React/validate/router-config'; +import { actionUpdate } from '../../React/realtime-location'; +import Root from '../../React/root.jsx'; +import { addEventHandlerOnPageUnload as addSessionStoreSaveEventHandlerOnPageUnload } from '../../React/client-session-store'; -import * as fullConfig from '__KOOT_PROJECT_CONFIG_FULL_PATHNAME__' +import i18nRegister from '../../i18n/register/isomorphic.client'; -import React from 'react' -import { hydrate } from 'react-dom' -// import { syncHistoryWithStore } from 'react-router-redux' -import routerMatch from 'react-router/lib/match' - -import validateRouterConfig from '../../React/validate/router-config' -import { actionUpdate } from '../../React/realtime-location' -import Root from '../../React/root.jsx' - -import i18nRegister from '../../i18n/register/isomorphic.client' - -let logCountRouterUpdate = 0 -let logCountHistoryUpdate = 0 +let logCountRouterUpdate = 0; +let logCountHistoryUpdate = 0; /** @type {Number} react-router match 允许的最长运行时间 (ms) */ -const maxRouterMatchTime = 5 * 1000 - +const maxRouterMatchTime = 5 * 1000; // ---------------------------------------------------------------------------- - /** * 判断变量是否是 Promise - * @param {*} v + * @param {*} v * @returns {Boolean} */ -const isPromise = (v) => { - return (typeof v === 'object' && typeof v.then === 'function') -} +const isPromise = v => { + return typeof v === 'object' && typeof v.then === 'function'; +}; /** * 处理生命周期方法,返回 Promise - * @param {Function|Promise} func + * @param {Function|Promise} func * @returns {Promise} */ -const parseLifecycleMethod = (func) => { - +const parseLifecycleMethod = func => { /** @type {Object} 生命周期方法传入的参数 */ const argsLifecycle = { store: Store, history: History, localeId: LocaleId - } + }; if (typeof func === 'function') { - const result = func(argsLifecycle) - if (isPromise(result)) - return result - return new Promise(resolve => resolve()) + const result = func(argsLifecycle); + if (isPromise(result)) return result; + return new Promise(resolve => resolve()); } - if (isPromise(func)) - return func - - return new Promise(resolve => resolve()) - -} + if (isPromise(func)) return func; + return new Promise(resolve => resolve()); +}; // ---------------------------------------------------------------------------- +const { router: routerConfig, client: clientConfig = {} } = fullConfig; -const { - router: routerConfig, - client: clientConfig = {} -} = fullConfig - -const { - before, - after, -} = clientConfig -const onRouterUpdate = clientConfig.routerUpdate || clientConfig.onRouterUpdate -const onHistoryUpdate = clientConfig.historyUpdate || clientConfig.onHistoryUpdate +const { before, after } = clientConfig; +const onRouterUpdate = clientConfig.routerUpdate || clientConfig.onRouterUpdate; +const onHistoryUpdate = + clientConfig.historyUpdate || clientConfig.onHistoryUpdate; /** @type {Object} 路由配置 */ -const routes = validateRouterConfig(routerConfig) +const routes = validateRouterConfig(routerConfig); /** @type {Object} 路由根组件 props */ const routerProps = { onUpdate: (...args) => { if (__DEV__ && logCountRouterUpdate < 2) { - console.log( - `🚩 [koot/client] ` + - `callback: onRouterUpdate`, - ...args - ) - logCountRouterUpdate++ + console.log(`🚩 [koot/client] callback: onRouterUpdate`, ...args); + logCountRouterUpdate++; } // if (__DEV__) console.log('router onUpdate', self.__LATHPATHNAME__, location.pathname) - if (typeof onRouterUpdate === 'function') - onRouterUpdate(...args) + if (typeof onRouterUpdate === 'function') onRouterUpdate(...args); } -} +}; // 从 SSR 结果中初始化当前环境的语种 -i18nRegister() +i18nRegister(); // 客户端流程正式开始 // 生命周期: 客户端流程正式开始前 -if (__DEV__) - console.log( - `🚩 [koot/client] ` + - `callback: before` - ) +if (__DEV__) console.log(`🚩 [koot/client] callback: before`); parseLifecycleMethod(before) .then(() => { + addSessionStoreSaveEventHandlerOnPageUnload(); + History.listen(location => { // 回调: browserHistoryOnUpdate // 正常路由跳转时,URL发生变化后瞬间会触发,顺序在react组件读取、渲染之前 // if (__DEV__) { // console.log('🌏 browserHistory update', location) // } - Store.dispatch(actionUpdate(location)) + Store.dispatch(actionUpdate(location)); if (__DEV__ && logCountHistoryUpdate < 2) { - console.log( - `🚩 [koot/client] ` + - `callback: onHistoryUpdate`, - [location, Store] - ) - logCountHistoryUpdate++ + console.log(`🚩 [koot/client] callback: onHistoryUpdate`, [ + location, + Store + ]); + logCountHistoryUpdate++; } if (typeof onHistoryUpdate === 'function') - onHistoryUpdate(location, Store) - }) + onHistoryUpdate(location, Store); + }); // const thisHistory = syncHistoryWithStore(History, Store) @@ -156,9 +136,9 @@ parseLifecycleMethod(before) // />, // document.getElementById('root') // ) - let isRendered = false + let isRendered = false; const doHydrate = () => { - if (isRendered) return + if (isRendered) return; hydrate( , document.getElementById('root') - ) - isRendered = true - } + ); + isRendered = true; + }; - let isRouterMatchComplete = false + let isRouterMatchComplete = false; return Promise.race([ - new Promise((resolve, reject) => setTimeout(() => { - if (!isRouterMatchComplete) - reject(new Error('routerMatch timeout')) - }, maxRouterMatchTime)), + new Promise((resolve, reject) => + setTimeout(() => { + if (!isRouterMatchComplete) + reject(new Error('routerMatch timeout')); + }, maxRouterMatchTime) + ), new Promise((resolve, reject) => { try { - routerMatch({ history: History, routes }, (err/*, redirectLocation, renderProps*/) => { - isRouterMatchComplete = true - if (err) return reject(err) + routerMatch({ history: History, routes }, ( + err /*, redirectLocation, renderProps*/ + ) => { + isRouterMatchComplete = true; + if (err) return reject(err); // console.log('\nrouter match', { err, ...args }) - resolve() - }) + resolve(); + }); } catch (e) { - isRouterMatchComplete = true - reject(e) + isRouterMatchComplete = true; + reject(e); } }) ]) .then(doHydrate) .catch(err => { - console.log('\n⚛️Page may flash blank due to `react-router` match failed!') - console.error(err) - doHydrate() - }) + console.log( + '\n⚛️Page may flash blank due to `react-router` match failed!' + ); + console.error(err); + doHydrate(); + }); }) .then(() => { // 生命周期: 客户端流程结束 if (__DEV__) { - console.log( - `🚩 [koot/client] ` + - `callback: after` - ) + console.log(`🚩 [koot/client] callback: after`); } }) - .then(parseLifecycleMethod(after)) + .then(parseLifecycleMethod(after)); diff --git a/packages/koot/ReactSPA/client/run.js b/packages/koot/ReactSPA/client/run.js index 5e4cb967..2542a7ad 100644 --- a/packages/koot/ReactSPA/client/run.js +++ b/packages/koot/ReactSPA/client/run.js @@ -1,72 +1,54 @@ // TODO: i18n -const React = require('react') -import ReactDOM from 'react-dom' -import history from "../../React/history" +import ReactDOM from 'react-dom'; +import history from '../../React/history'; // -import { - localeId as LocaleId, - store as Store, - getHistory -} from '../../index' -import { actionUpdate } from '../../React/realtime-location' -import Root from '../../React/root.jsx' -import validateRouterConfig from '../../React/validate/router-config' +import { localeId as LocaleId, store as Store, getHistory } from '../../index'; +import { actionUpdate } from '../../React/realtime-location'; +import Root from '../../React/root.jsx'; +import validateRouterConfig from '../../React/validate/router-config'; +import { addEventHandlerOnPageUnload as addSessionStoreSaveEventHandlerOnPageUnload } from '../../React/client-session-store'; + +const React = require('react'); // import { // reducerLocaleId as i18nReducerLocaleId, // reducerLocales as i18nReducerLocales, // } from 'koot/i18n/redux' // import i18nRegister from 'koot/i18n/register/spa.client' - // ============================================================================ // 设置常量 & 变量 // ============================================================================ -let logCountRouterUpdate = 0 -let logCountHistoryUpdate = 0 - +let logCountRouterUpdate = 0; +let logCountHistoryUpdate = 0; +export default ({ router, client }) => { + addSessionStoreSaveEventHandlerOnPageUnload(); -export default ({ - router, - client -}) => { // console.log({ // router, // redux, // client // }) - const { - before, - after, - } = client - const onRouterUpdate = client.routerUpdate || client.onRouterUpdate - const onHistoryUpdate = client.historyUpdate || client.onHistoryUpdate - - - - + const { before, after } = client; + const onRouterUpdate = client.routerUpdate || client.onRouterUpdate; + const onHistoryUpdate = client.historyUpdate || client.onHistoryUpdate; // ============================================================================ // i18n 初始化 // ============================================================================ // if (i18n) i18nRegister(i18n, store) - - - - // ============================================================================ // 路由初始化 // ============================================================================ - const routes = validateRouterConfig(router) - if (typeof routes.path === 'undefined') - routes.path = '/' - const History = getHistory() + const routes = validateRouterConfig(router); + if (typeof routes.path === 'undefined') routes.path = '/'; + const History = getHistory(); // const thisHistory = syncHistoryWithStore(History, Store) const routerConfig = { // history: syncHistoryWithStore(memoryHistory, store), @@ -75,17 +57,15 @@ export default ({ onUpdate: (...args) => { if (__DEV__ && logCountRouterUpdate < 2) { console.log( - `🚩 [koot/client] ` + - `callback: onRouterUpdate`, + `🚩 [koot/client] ` + `callback: onRouterUpdate`, ...args - ) - logCountRouterUpdate++ + ); + logCountRouterUpdate++; } // if (__DEV__) console.log('router onUpdate', self.__LATHPATHNAME__, location.pathname) - if (typeof onRouterUpdate === 'function') - onRouterUpdate(...args) + if (typeof onRouterUpdate === 'function') onRouterUpdate(...args); } - } + }; // const history = hashHistory // if (__CLIENT__) self.routerHistory = memoryHistory // if (__CLIENT__) self.routerHistory = hashHistory @@ -96,23 +76,19 @@ export default ({ // console.log('🌏 browserHistory update', location) // } // console.log(actionUpdate(location)) - Store.dispatch(actionUpdate(location)) + Store.dispatch(actionUpdate(location)); // console.log(store.getState()) if (__DEV__ && logCountHistoryUpdate < 2) { - console.log( - `🚩 [koot/client] ` + - `callback: onHistoryUpdate`, - [location, Store] - ) - logCountHistoryUpdate++ + console.log(`🚩 [koot/client] ` + `callback: onHistoryUpdate`, [ + location, + Store + ]); + logCountHistoryUpdate++; } if (typeof onHistoryUpdate === 'function') - onHistoryUpdate(location, Store) - }) - - - + onHistoryUpdate(location, Store); + }); // ============================================================================ // React 初始化 @@ -120,48 +96,45 @@ export default ({ if (__DEV__) console.log( - `🚩 [koot/client] ` + - `callback: before`, + `🚩 [koot/client] ` + `callback: before` // args - ) + ); if (__DEV__) console.log( - `🚩 [koot/client] ` + - `callback: before`, + `🚩 [koot/client] ` + `callback: before` // args - ) + ); const beforePromise = (() => { - const _before = typeof before === 'function' ? before() : before + const _before = typeof before === 'function' ? before() : before; if (typeof _before === 'object' && typeof _before.then === 'function') { - return _before + return _before; } return new Promise(resolve => { - if (typeof _before === 'function') - _before() - resolve() - }) - })() + if (typeof _before === 'function') _before(); + resolve(); + }); + })(); beforePromise .then(() => { if (__DEV__) - console.log( - `🚩 [koot/client] ` + - `callback: after`, - { Store, history } - ) + console.log(`🚩 [koot/client] ` + `callback: after`, { + Store, + history + }); if (typeof after === 'function') after({ - Store, history - }) + Store, + history + }); }) .then(() => { // console.log('store', store) // console.log('routerConfig', routerConfig) - const { history, routes, ...ext } = routerConfig + const { history, routes, ...ext } = routerConfig; // console.log(routes) ReactDOM.render( @@ -172,8 +145,8 @@ export default ({ {...ext} />, document.getElementById('root') - ) + ); - return true - }) -} + return true; + }); +}; diff --git a/packages/koot/defaults/koot-config.js b/packages/koot/defaults/koot-config.js index 4ea3dd99..1ad51202 100644 --- a/packages/koot/defaults/koot-config.js +++ b/packages/koot/defaults/koot-config.js @@ -2,6 +2,7 @@ module.exports = { type: 'react', dist: './dist', cookiesToStore: true, + sessionStore: false, i18n: false, pwa: true, aliases: {}, @@ -21,5 +22,5 @@ module.exports = { classNameHashLength: 6, bundleVersionsKeep: 2, - devPort: 3080, -} + devPort: 3080 +}; diff --git a/packages/koot/package.json b/packages/koot/package.json index e0f76618..87fcd9b5 100644 --- a/packages/koot/package.json +++ b/packages/koot/package.json @@ -107,7 +107,7 @@ "postcss-loader": "3.0.0", "react": "16.8.6", "react-dom": "16.8.6", - "react-hot-loader": "4.9.0", + "react-hot-loader": "4.11.0", "react-redux": "5.1.1", "react-router": "3.2.1", "react-router-redux": "4.0.8", diff --git a/packages/koot/utils/init-node-env.js b/packages/koot/utils/init-node-env.js index c41a7df3..c4166d04 100644 --- a/packages/koot/utils/init-node-env.js +++ b/packages/koot/utils/init-node-env.js @@ -1,7 +1,7 @@ -const path = require('path') -const fs = require('fs-extra') +const path = require('path'); +const fs = require('fs-extra'); -const defaultsPWA = require('../defaults/pwa') +const defaultsPWA = require('../defaults/pwa'); /** * 初始化 node.js 环境变量 @@ -32,11 +32,9 @@ module.exports = () => { // 服务器端口 SERVER_PORT: (() => { - if (process.env.WEBPACK_BUILD_ENV === 'dev') - return '3000' - if (typeof __SERVER_PORT__ !== 'undefined') - return __SERVER_PORT__ - return '8080' + if (process.env.WEBPACK_BUILD_ENV === 'dev') return '3000'; + if (typeof __SERVER_PORT__ !== 'undefined') return __SERVER_PORT__; + return '8080'; })(), // 服务器端口 (开发环境主服务器) @@ -106,10 +104,13 @@ module.exports = () => { // 客户端打包子目录名 KOOT_CLIENT_BUNDLE_SUBFOLDER: '', - } + + // 配置: sessionStore + KOOT_SESSION_STORE: JSON.stringify(false) + }; for (let key in defaults) { if (typeof process.env[key] === 'undefined') { - process.env[key] = defaults[key] + process.env[key] = defaults[key]; } } -} +}; diff --git a/test/projects/simple/package.json b/test/projects/simple/package.json index 45aca378..00270d11 100644 --- a/test/projects/simple/package.json +++ b/test/projects/simple/package.json @@ -103,7 +103,7 @@ "postcss-loader": "3.0.0", "react": "16.8.6", "react-dom": "16.8.6", - "react-hot-loader": "4.9.0", + "react-hot-loader": "4.11.0", "react-redux": "5.1.1", "react-router": "3.2.1", "react-router-redux": "4.0.8", diff --git a/test/projects/standard/koot.config.js b/test/projects/standard/koot.config.js index 1d65466a..7895e270 100644 --- a/test/projects/standard/koot.config.js +++ b/test/projects/standard/koot.config.js @@ -25,6 +25,7 @@ module.exports = { store: './src/store/create-method-1', cookiesToStore: 'all', + sessionStore: true, i18n: { // type: 'redux', // 仅影响 client-prod 环境 diff --git a/test/projects/standard/package.json b/test/projects/standard/package.json index 3418d124..bda844af 100644 --- a/test/projects/standard/package.json +++ b/test/projects/standard/package.json @@ -115,7 +115,7 @@ "postcss-loader": "3.0.0", "react": "16.8.6", "react-dom": "16.8.6", - "react-hot-loader": "4.9.0", + "react-hot-loader": "4.11.0", "react-redux": "5.1.1", "react-router": "3.2.1", "react-router-redux": "4.0.8", diff --git a/test/projects/standard/src/components/app.jsx b/test/projects/standard/src/components/app.jsx index 036f72ab..b18c32ee 100644 --- a/test/projects/standard/src/components/app.jsx +++ b/test/projects/standard/src/components/app.jsx @@ -1,20 +1,20 @@ -import React from 'react' -import { store, history, localeId, extend } from 'koot' +import React from 'react'; +import { store, history, localeId, extend } from 'koot'; // console.log('[App]', { store, history, localeId }) -import Nav from './_layout/nav' -import Main from './_layout/main' +import Nav from './_layout/nav'; +import Main from './_layout/main'; -let stateShowed = false +let stateShowed = false; @extend({ connect: state => { if (__CLIENT__ && __DEV__ && !stateShowed) { - console.log('root: redux store conected', state) - stateShowed = true + console.log('root: redux store conected', state); + stateShowed = true; } - return {} + return {}; }, styles: require('./app.less'), name: 'App' @@ -22,12 +22,12 @@ let stateShowed = false class App extends React.Component { componentDidMount() { if (__DEV__) { - console.log('redux store', store) - console.log('history', history) + console.log('redux store', store); + console.log('history', history); } } componentDidCatch(error, info) { - console.log('ERROR', error, info) + console.log('ERROR', error, info); // Display fallback UI // this.setState({ hasError: true }) // You can also log the error to an error reporting service @@ -37,6 +37,11 @@ class App extends React.Component { // console.log(this.props) } render() { + // console.log({ + // 'process.env.KOOT_SESSION_STORE': JSON.parse( + // process.env.KOOT_SESSION_STORE + // ) + // }); // console.log('[App] render', { store, history, localeId }) // console.log('App render', { // 'in __KOOT_SSR__': __KOOT_SSR__.LocaleId @@ -49,8 +54,8 @@ class App extends React.Component {
- ) + ); } } -export default App +export default App;