├── README.md
├── web
├── pages
│ ├── page
│ │ ├── index.less
│ │ └── index.jsx
│ └── page2
│ │ ├── index.less
│ │ └── index.jsx
├── reducer
│ ├── index.js
│ └── counter.js
├── action
│ └── count.js
├── router.js
├── store
│ └── index.js
├── render
│ ├── clientRouter.js
│ └── serverRouter.js
└── components
│ └── layout
│ └── index.jsx
├── .gitignore
├── app
├── router.js
├── controller
│ └── home.js
├── view
│ └── index.njk
└── build
│ ├── page2.js
│ └── page1.js
├── .vscode
└── launch.json
├── .babelrc
├── package.json
├── index.js
└── webpack.config.js
/README.md:
--------------------------------------------------------------------------------
1 | # koa-react-ssr-render
2 | 基于koa的react服务器渲染
3 |
4 | > npm i 安装依赖
5 |
6 | > npm run build
7 |
8 | > 新开终端 执行 npm start
--------------------------------------------------------------------------------
/web/pages/page/index.less:
--------------------------------------------------------------------------------
1 | .title-name {
2 | font-size: 18px;
3 | color: #2F3872;
4 | letter-spacing: 0;
5 | text-align: center;
6 | font-weight: 500
7 |
8 | }
--------------------------------------------------------------------------------
/web/pages/page2/index.less:
--------------------------------------------------------------------------------
1 | .title-name {
2 | font-size: 18px;
3 | color: #2F3872;
4 | letter-spacing: 0;
5 | text-align: center;
6 | font-weight: 500
7 |
8 | }
--------------------------------------------------------------------------------
/web/reducer/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 | import * as page from './counter'
3 |
4 | const rootReducer = combineReducers({
5 | ...page
6 | })
7 |
8 | export default rootReducer
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # production
7 | /dist
8 |
9 | /public
10 | # misc
11 | .DS_Store
12 | npm-debug.log*
13 | package-lock.json
--------------------------------------------------------------------------------
/app/router.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * @param {router 实例化对象} app
4 | */
5 |
6 | const home = require('./controller/home');
7 |
8 | module.exports = (router) => {
9 | router.get('/',home.renderHtml);
10 | router.get('/page2',home.renderHtml);
11 | router.get('/favicon.ico',home.favicon);
12 | router.get('/test',home.test);
13 | }
14 |
--------------------------------------------------------------------------------
/web/action/count.js:
--------------------------------------------------------------------------------
1 | export const set = (value) => ({
2 | type: 'SET_COUNTER'
3 | });
4 |
5 | export const setData = (value) => ({
6 | type: 'SET_VALUE',
7 | value: value
8 | });
9 |
10 | export const computedAdd = () => (dispatch, getState) => {
11 | dispatch(set());
12 | }
13 |
14 |
15 | export const setValue = (val) => (dispatch, getState) => {
16 |
17 | dispatch(setData(val));
18 | }
--------------------------------------------------------------------------------
/app/controller/home.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * 渲染react页面
4 | */
5 |
6 | exports.renderHtml = async (ctx) => {
7 | let initState = ctx.query.state ? JSON.parse(ctx.query.state) : null;
8 | ctx.renderServer("page1", {store: JSON.stringify(initState ? initState : { counter: 1 }) });
9 | }
10 | exports.favicon = (ctx) => {
11 | ctx.body = 'xxx';
12 | }
13 |
14 | exports.test = (ctx) => {
15 | ctx.body = {
16 | data: `测试数据`
17 | }
18 | }
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "node",
9 | "request": "launch",
10 | "name": "Launch Program",
11 | "program": "${workspaceFolder}\\index.js"
12 | }
13 | ]
14 | }
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 |
2 |
3 | {
4 | "presets": ["@babel/preset-react",
5 | ["@babel/preset-env",{
6 | "targets": {
7 | "browsers": [
8 | "ie >= 9",
9 | "ff >= 30",
10 | "chrome >= 34",
11 | "safari >= 7",
12 | "opera >= 23",
13 | "bb >= 10"
14 | ]
15 | }
16 | }]
17 | ],
18 | "plugins": [
19 | [
20 | "import",
21 | { "libraryName": "antd", "style": true }
22 | ]
23 | ]
24 | }
--------------------------------------------------------------------------------
/app/view/index.njk:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | koa-React服务器渲染
8 | {{ link | safe }}
9 |
10 |
11 |
12 |
13 | {{ html | safe }}
14 |
15 |
16 |
19 | {{ script | safe }}
20 |
--------------------------------------------------------------------------------
/web/reducer/counter.js:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | export const counter = (state = 0, action) => {
5 | switch (action.type) {
6 | case 'SET_COUNTER':
7 |
8 | return state += 1;
9 | break;
10 | default:
11 | return state;
12 | break;
13 | }
14 | }
15 |
16 | export const value = (state = null, action) => {
17 | switch (action.type) {
18 | case 'SET_VALUE':
19 | return action.value;
20 | break;
21 | default:
22 | return state;
23 | break;
24 | }
25 | }
--------------------------------------------------------------------------------
/web/router.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StaticRouter, Switch } from 'react-router'
3 | import { BrowserRouter, Route, Link } from "react-router-dom";
4 | import Page1 from './pages/page/index.jsx';
5 | import Page2 from './pages/page2/index.jsx';
6 | import Layout from './components/layout';
7 |
8 | function RenderRouter() {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 | )
17 | }
18 |
19 | export default RenderRouter;
--------------------------------------------------------------------------------
/web/store/index.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux'
2 | import thunk from 'redux-thunk'
3 | import rootReducer from '../reducer'
4 |
5 | const configureStore = (initStore) => {
6 | const store = createStore(
7 | rootReducer,
8 | initStore,
9 | applyMiddleware(thunk)
10 | )
11 |
12 | // if (module.hot) {
13 | // // Enable Webpack hot module replacement for reducers
14 | // module.hot.accept('../reducers', () => {
15 | // store.replaceReducer(rootReducer)
16 | // })
17 | // }
18 |
19 | return store
20 | }
21 |
22 | export default configureStore
--------------------------------------------------------------------------------
/web/render/clientRouter.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { BrowserRouter } from "react-router-dom";
4 | import { Provider } from 'react-redux';
5 | import Router from '../router';
6 | import configStore from '../store';
7 |
8 | let initStore = window.__INIT_STORE__;
9 | let store = configStore(initStore);
10 |
11 | function ClientRender() {
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
19 | )
20 | }
21 |
22 | ReactDOM.hydrate(, document.querySelector('#app'));
--------------------------------------------------------------------------------
/web/render/serverRouter.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StaticRouter } from 'react-router'
3 | import { Provider } from 'react-redux'
4 | import CreateStore from '../store/index.js'
5 | import Router from '../router.js';
6 |
7 | function ServerRender(req, initStore) {
8 | let store = CreateStore(JSON.parse(initStore.store));
9 |
10 | return (props, context) => {
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
18 | )
19 | }
20 |
21 | }
22 |
23 | export default ServerRender;
24 |
--------------------------------------------------------------------------------
/web/components/layout/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux'
3 | import { bindActionCreators } from 'redux'
4 | import * as CounterActions from 'action/count';
5 |
6 | class Layout extends Component {
7 | constructor() {
8 | super()
9 | }
10 |
11 | render() {
12 | return (
13 |
14 |
15 | 首页
16 | 次页
17 | {this.props.counter}
18 | {
19 | this.props.children
20 | }
21 |
22 | )
23 | }
24 | }
25 | const mapStateToProps = (state) => ({
26 | counter: state.counter,
27 | state: state
28 | })
29 |
30 | const mapDispatchToProps = (dispatch) => {
31 | return bindActionCreators(CounterActions, dispatch)
32 | }
33 |
34 | export default connect(mapStateToProps, mapDispatchToProps)(Layout)
--------------------------------------------------------------------------------
/web/pages/page/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { bindActionCreators } from 'redux'
3 | import { connect } from 'react-redux'
4 | import * as CounterActions from 'action/count';
5 | import axios from 'axios';
6 | import './index.less';
7 |
8 | class Counter extends React.Component {
9 | constructor() {
10 | super();
11 | this.state = {
12 | test: '我是测试'
13 | }
14 | }
15 |
16 | onClick() {
17 | this.props.computedAdd();
18 | }
19 |
20 | componentWillMount() {
21 | axios('http://127.0.0.1:3000/test').then((json) => {
22 | return json.data;
23 | }).then((res) => {
24 | this.props.setValue(res.data);
25 | })
26 | }
27 |
28 | render() {
29 |
30 | return
31 |
32 | {this.props.counter}
33 |
34 |
35 | {this.state.test}
36 |
37 |
38 | }
39 | }
40 | const mapStateToProps = (state) => {
41 | return {
42 | counter: state.counter,
43 | value: state.value
44 | }
45 | }
46 |
47 | const mapDispatchToProps = (dispatch) => {
48 | return bindActionCreators(CounterActions, dispatch)
49 | }
50 |
51 | export default connect(mapStateToProps, mapDispatchToProps)(Counter)
52 |
--------------------------------------------------------------------------------
/web/pages/page2/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.less';
4 |
5 | function onClick() {
6 | console.log("我是页面2")
7 | }
8 | function Title(props) {
9 | return
10 | 专栏 4Ark 5小时前 程序员 分享一些好用的网站 73 5 专栏 saltfish666 5小时前 Vue.js Vue 源码中一些util函数 45 3 专栏 嘉乐MrMaple 1小时前 程序员年终总结 一个技术创业者的2018年度回顾和总结 | 掘金年度征文 4 3 小册修言 前端性能优化原理与实践 购买人数: 4886 特价: 19.9元 分享 专栏 極楽 1小时前 Node.js微信小程序 微信小程序websocket聊天室 4 专栏 call_me_R 3小时前 前端前端框架 【译】框架与库的差异 12 6 专栏 大翰仔仔 3小时前 面试 刷前端面经笔记(七) 5 专栏 小生方勤 4小时前 前端 【前端词典】继承(二) - “回”的八种写法 20 HollisChuang 6小时前 程序员编程语言 指引趋势与方向!2019开发者调查报告出炉 5 6 专栏 boomyao 3小时前 React.js React是如何区分class和function的? 9 专栏 wznonstop 3小时前 前端掘金翻译计划 [译] 2019 前端性能优化年度总结 — 第五部分 7 热 专栏 frontdog 17小时前 前端 web页面录屏实现 89 7 专栏 超人汪小建 6小时前 前端算法 基于桶的基数排序 12 专栏 神奇排骨 3小时前 前端 浅析前端的模块化 8 专栏 我是一个前端 3小时前 JavaScript Javascript 事件循环event loop 6 freefrontend 5小时前 CSS 14 CSS Button Click Effects 6 专栏 boomyao 22小时前 React.js 为什么我们要添加 super(props) ? 36 3 热 张熠 1天前 Vue.jsTypeScript 值得一看,Vue 作者尤雨溪的回答【TypeScript 不适合在 vue 业务开发中使用吗?】 45 9 专栏 南波 22小时前 JavaScript面试 JS专题之继承 29 1 专栏 MarcusY 1天前 CSS 【译】通过css,用一个div做一个芝士汉堡 51 14 简单卟容易 1天前 Vue.js vue修饰符--可能是东半球最详细的文档(滑稽) 114 4 专栏 sea_ljf 1天前 JavaScript 知多一点二进制中的负数 45 6 热 huangsw 1年前 前端 强烈推荐--基于 vue2.x table 组件 386 9 专栏 serialcoder 1天前 JavaScript函数式编程 再谈 JavaScript 函数式编程的适用性 45 2 专栏 南波 1天前 JavaScript面试 JS专题之垃圾回收 66 热 专栏 芬达Tz 2天前 CSS 如何使用css绘制钻石 66 5 专栏 caiyongji 8月前 面试Java 你的知识死角不能否定你的技术能力 38 10 专栏 掘墓人4449 1天前 微信 微信支付申请相关问
11 |
12 |
13 | }
14 |
15 | // if(process.env.WEBPACK_WEB) {
16 | // ReactDOM.hydrate(, document.querySelector('#app'));
17 | // } else {
18 | export default Title;
19 | // }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-ssr",
3 | "version": "0.0.1",
4 | "description": "react服务器渲染模板",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "build": "cross-env NODE_ENV=production webpack --progress",
9 | "start": "node index.js"
10 | },
11 | "keywords": [
12 | "react",
13 | "ssr",
14 | "node",
15 | "javascript",
16 | "babel",
17 | "import",
18 | "server"
19 | ],
20 | "author": "ruanguangguang",
21 | "license": "MIT",
22 | "dependencies": {
23 | "@babel/core": "^7.2.2",
24 | "@babel/preset-env": "^7.3.1",
25 | "@babel/preset-react": "^7.0.0",
26 | "autoprefixer": "^9.4.7",
27 | "axios": "^0.18.0",
28 | "babel-cli": "^6.26.0",
29 | "babel-loader": "^8.0.5",
30 | "babel-plugin-import": "^1.11.0",
31 | "cross-env": "^5.2.0",
32 | "css-loader": "^2.1.0",
33 | "emotion": "^10.0.6",
34 | "emotion-server": "^10.0.6",
35 | "isomorphic-style-loader": "^4.0.0",
36 | "koa": "^2.7.0",
37 | "koa-nunjucks-2": "^3.0.2",
38 | "koa-router": "^7.4.0",
39 | "koa-static": "^5.0.0",
40 | "less": "^3.0.1",
41 | "less-loader": "^4.1.0",
42 | "mini-css-extract-plugin": "^0.4.3",
43 | "postcss-loader": "^3.0.0",
44 | "precss": "^4.0.0",
45 | "react": "^16.7.0",
46 | "react-dom": "^16.7.0",
47 | "react-redux": "^6.0.0",
48 | "react-router": "^4.3.1",
49 | "react-router-dom": "^4.3.1",
50 | "redux": "^4.0.1",
51 | "redux-thunk": "^2.3.0",
52 | "style-loader": "^0.23.1",
53 | "webpack": "^4.29.0",
54 | "webpack-cli": "^3.2.1",
55 | "webpack-dev-server": "^3.1.14",
56 | "webpack-manifest-plugin": "^2.0.4",
57 | "webpack-node-externals": "^1.7.2"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const Koa = require('koa');
2 | const app = new Koa();
3 | const path = require('path');
4 | const koaNunjucks = require('koa-nunjucks-2');
5 | const React = require('react');
6 | const KoaRouter = require('koa-router');
7 | const ReactDOMServer = require('react-dom/server');
8 | const koaStatic = require('koa-static');
9 | const router = new KoaRouter();
10 |
11 | const routerManagement = require('./app/router');
12 | const manifest = require('./public/manifest.json');
13 |
14 |
15 | /**
16 | * 处理链接
17 | * @param {*要进行服务器渲染的文件名默认是build文件夹下的文件} fileName
18 | */
19 | function handleLink(fileName, req, defineParams) {
20 | let obj = {};
21 | fileName = fileName.indexOf('.') !== -1 ? fileName.split('.')[0] : fileName;
22 |
23 | try {
24 | obj.script = ``;
25 | } catch (error) {
26 | console.error(new Error(error));
27 | }
28 | try {
29 | obj.link = ``;
30 |
31 | } catch (error) {
32 | console.error(new Error(error));
33 | }
34 | //服务器渲染
35 | const dom = require(path.join(process.cwd(),`app/build/${fileName}.js`)).default;
36 | let element = React.createElement(dom(req, defineParams));
37 | obj.html = ReactDOMServer.renderToString(element);
38 |
39 | return obj;
40 | }
41 |
42 | /**
43 | * 服务器渲染,渲染HTML,渲染模板
44 | * @param {*} ctx
45 | */
46 | function renderServer(ctx, next) {
47 | return async (fileName, defineParams) => {
48 |
49 | let obj = handleLink(fileName, ctx.req, defineParams);
50 | // 处理自定义参数
51 | defineParams = String(defineParams) === "[object Object]" ? defineParams : {};
52 | obj = Object.assign(obj, defineParams);
53 | await ctx.render('index', obj);
54 | }
55 | }
56 | /**
57 | * 模板渲染
58 | */
59 | app.use(koaNunjucks({
60 | ext: 'njk',
61 | path: path.join(__dirname, 'app/view'),
62 | nunjucksConfig: {
63 | trimBlocks: true
64 | }
65 | }));
66 | /**
67 | * 设置静态资源
68 | */
69 | app.use(koaStatic(path.resolve(__dirname, './public'), {
70 | maxage: 0, //浏览器缓存max-age(以毫秒为单位)
71 | hidden: false, //允许传输隐藏文件
72 | index: 'index.html', // 默认文件名,默认为'index.html'
73 | defer: true, //如果为true,则使用后return next(),允许任何下游中间件首先响应。
74 | gzip: true, //当客户端支持gzip时,如果存在扩展名为.gz的请求文件,请尝试自动提供文件的gzip压缩版本。默认为true。
75 | }));
76 |
77 |
78 |
79 | /**
80 | * 渲染Html
81 | */
82 | app.use(async (ctx, next) => {
83 | ctx.renderServer = renderServer(ctx);
84 | await next();
85 | });
86 |
87 |
88 | /**
89 | * 注册路由
90 | */
91 | routerManagement(router);
92 | app.use(router.routes()).use(router.allowedMethods());
93 |
94 | app.listen(3000, () => {
95 | console.log("服务器已启动,请访问http://127.0.0.1:3000")
96 | });
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 |
2 | const path = require('path');
3 | const MiniCssExtractPlugin = require("mini-css-extract-plugin")
4 | const nodeExternals = require('webpack-node-externals');
5 | const ManifestPlugin = require('webpack-manifest-plugin');
6 |
7 | const resolve = {
8 | alias: {
9 | action: path.join(__dirname, './web/action')
10 | },
11 | extensions: ['.js', '.jsx']
12 | }
13 |
14 | //服务端配置
15 | const serverConfig = {
16 | target: 'node',
17 | entry: {
18 | page1: './web/render/serverRouter.js',
19 | },
20 | resolve,
21 | output: {
22 | filename: '[name].js',
23 | path: path.resolve(__dirname, './app/build'),
24 | libraryTarget: 'commonjs'
25 | },
26 | mode: process.env.NODE_ENV,
27 | externals: [nodeExternals()],
28 | module: {
29 | rules: [
30 | {
31 | test: /\.(jsx|js)?$/,
32 | exclude: /node_modules/,
33 | use: {
34 | loader: 'babel-loader'
35 | }
36 | },
37 | {
38 | test: /\.(css|less)$/,
39 | use: [
40 | {
41 | loader: 'css-loader',
42 | options: {
43 | importLoaders: 1
44 | }
45 | },
46 | {
47 | loader: 'less-loader',
48 | }
49 | ]
50 | }
51 | ]
52 | }
53 | };
54 | //客户端
55 | const clientConfig = {
56 | entry: {
57 | page1: './web/render/clientRouter.js'
58 | },
59 | output: {
60 | filename: '[name].js',
61 | path: path.resolve(__dirname, './public'),
62 | },
63 | mode: process.env.NODE_ENV,
64 | resolve,
65 | module: {
66 | rules: [
67 | {
68 | test: /\.(jsx|js)?$/,
69 | exclude: /node_modules/,
70 | use: {
71 | loader: 'babel-loader'
72 | }
73 | },
74 | {
75 | test: /\.(css|less)$/,
76 | use: [
77 | {
78 | loader: MiniCssExtractPlugin.loader,
79 | },
80 | {
81 | loader: 'css-loader'
82 | },
83 | {
84 | loader: 'postcss-loader',
85 | options: {
86 | plugins: [
87 | require('precss'),
88 | require('autoprefixer')
89 | ],
90 | }
91 | },
92 | {
93 | loader: 'less-loader',
94 | options: {
95 | javascriptEnabled: true,
96 | // modifyVars: theme //antd默认主题样式
97 | }
98 | }
99 | ],
100 | },
101 | ]
102 | },
103 |
104 | plugins: [
105 | // 提取样式,生成单独文件
106 | new MiniCssExtractPlugin({
107 | filename: `[name].css`,
108 | chunkFilename: `[name].chunk.css`
109 | }),
110 | new ManifestPlugin()
111 | ]
112 |
113 | };
114 |
115 | module.exports = [serverConfig, clientConfig]
--------------------------------------------------------------------------------
/app/build/page2.js:
--------------------------------------------------------------------------------
1 | !function(o,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e=t();for(var n in e)("object"==typeof exports?exports:o)[n]=e[n]}}(global,function(){return function(o){var t={};function e(n){if(t[n])return t[n].exports;var r=t[n]={i:n,l:!1,exports:{}};return o[n].call(r.exports,r,r.exports,e),r.l=!0,r.exports}return e.m=o,e.c=t,e.d=function(o,t,n){e.o(o,t)||Object.defineProperty(o,t,{enumerable:!0,get:n})},e.r=function(o){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(o,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(o,"__esModule",{value:!0})},e.t=function(o,t){if(1&t&&(o=e(o)),8&t)return o;if(4&t&&"object"==typeof o&&o&&o.__esModule)return o;var n=Object.create(null);if(e.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:o}),2&t&&"string"!=typeof o)for(var r in o)e.d(n,r,function(t){return o[t]}.bind(null,r));return n},e.n=function(o){var t=o&&o.__esModule?function(){return o.default}:function(){return o};return e.d(t,"a",t),t},e.o=function(o,t){return Object.prototype.hasOwnProperty.call(o,t)},e.p="",e(e.s=5)}([function(o,t){o.exports=require("react")},function(o,t){o.exports=require("react-dom")},function(o,t,e){"use strict";o.exports=function(o){var t=[];return t.toString=function(){return this.map(function(t){var e=function(o,t){var e=o[1]||"",n=o[3];if(!n)return e;if(t&&"function"==typeof btoa){var r=(u=n,"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(u))))+" */"),i=n.sources.map(function(o){return"/*# sourceURL="+n.sourceRoot+o+" */"});return[e].concat(i).concat([r]).join("\n")}var u;return[e].join("\n")}(t,o);return t[2]?"@media "+t[2]+"{"+e+"}":e}).join("")},t.i=function(o,e){"string"==typeof o&&(o=[[null,o,""]]);for(var n={},r=0;r0&&void 0!==arguments[0]?arguments[0]:0;switch((arguments.length>1?arguments[1]:void 0).type){case"SET_COUNTER":return t+1;default:return t}},b=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,e=arguments.length>1?arguments[1]:void 0;switch(e.type){case"SET_VALUE":return e.value;default:return t}};function y(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}var v=Object(f.combineReducers)(function(t){for(var e=1;e