diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..001dbb7 --- /dev/null +++ b/.babelrc @@ -0,0 +1,6 @@ +{ + "presets": ["react-app"], + "plugins": [ + "transform-flow-strip-types" + ] +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..026ef9e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.json] +indent_style = space +indent_size = 2 diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..b219298 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,71 @@ +{ + "extends": "airbnb", + + "parser": "babel-eslint", + "plugins": [ + "flowtype" + ], + + rules: { + + "arrow-parens": [0], + + "new-cap": [2, { + "capIsNewExceptions": [ + "List", + "Map", + "Seq", + "Set", + "Range", + "Immutable.Map", + "Immutable.Set", + "Immutable.List", + "Immutable.Seq", + "Immutable.Range" + ] + }], + + "import/prefer-default-export": [0], + "react/jsx-no-bind": [1], + "react/jsx-filename-extension": [0], + "import/no-extraneous-dependencies": "off", + "react/forbid-prop-types": [1], + + "flowtype/define-flow-type": 1, + "flowtype/require-parameter-type": 1, + "flowtype/require-return-type": [ + 1, + "always", + { + "annotateUndefined": "never" + } + ], + "flowtype/space-after-type-colon": [ + 1, + "always" + ], + "flowtype/space-before-type-colon": [ + 1, + "never" + ], + "flowtype/type-id-match": [ + 1, + "^(([A-Z][a-z0-9]+)+Type)$|^Props$" + ], + "flowtype/use-flow-type": 1, + "flowtype/valid-syntax": 1 + }, + + "settings": { + "flowtype": { + "onlyFilesWithFlowAnnotation": true + } + }, + + "parserOptions": { + "ecmaFeatures": { + "experimentalObjectRestSpread": true + }, + } + +} diff --git a/.flowconfig b/.flowconfig new file mode 100644 index 0000000..479edc4 --- /dev/null +++ b/.flowconfig @@ -0,0 +1,12 @@ +[libs] +./node_modules/fbjs/flow/lib + +[options] +suppress_type=$FlowIssue +module.name_mapper='.*\(.pcss\)' -> 'empty/object' +module.name_mapper.extension='png' -> '/ImageModule.js.flow' +module.name_mapper.extension='jpg' -> '/ImageModule.js.flow' +module.name_mapper.extension='gif' -> '/ImageModule.js.flow' + +[version] +0.34.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aae0c07 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +config.client.js +config.server.js +dist/* +coverage/ diff --git a/.storybook/config.js b/.storybook/config.js new file mode 100644 index 0000000..6182c06 --- /dev/null +++ b/.storybook/config.js @@ -0,0 +1,8 @@ +import { configure } from '@kadira/storybook'; +const req = require.context('../src', true, /\.stories\.js$/); + +function loadStories() { + req.keys().forEach((filename) => req(filename)); +} + +configure(loadStories, module); diff --git a/.storybook/head.html b/.storybook/head.html new file mode 100644 index 0000000..a81b852 --- /dev/null +++ b/.storybook/head.html @@ -0,0 +1,21 @@ + + + diff --git a/.storybook/webpack.config.babel.js b/.storybook/webpack.config.babel.js new file mode 100644 index 0000000..8b43190 --- /dev/null +++ b/.storybook/webpack.config.babel.js @@ -0,0 +1,9 @@ +import { getCommonLoaders, getPostCss } from '../webpack.config.babel'; + +// Do NOT refactor as an export. Breaks the kludge required by Storybook. +module.exports = { + module: { + loaders: getCommonLoaders('development').toJS(), + }, + postcss: getPostCss() +}; diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js new file mode 100644 index 0000000..c080ab7 --- /dev/null +++ b/.storybook/webpack.config.js @@ -0,0 +1,3 @@ +// This kludge is required because Storybook don't do babel version of config. +require('babel-register'); +module.exports = require('./webpack.config.babel.js'); diff --git a/ImageModule.js.flow b/ImageModule.js.flow new file mode 100644 index 0000000..6b73761 --- /dev/null +++ b/ImageModule.js.flow @@ -0,0 +1,3 @@ +declare module "ImageModule" { + declare export default string; +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..70fb80d --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2015 Mikko Forsström & Panu Leppäniemi +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of the authors nor the names of contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/config.client.example.js b/config.client.example.js new file mode 100644 index 0000000..fab55d5 --- /dev/null +++ b/config.client.example.js @@ -0,0 +1,3 @@ +export default { + api: 'http://localhost:8890', +}; diff --git a/config.server.example.js b/config.server.example.js new file mode 100644 index 0000000..cada6c5 --- /dev/null +++ b/config.server.example.js @@ -0,0 +1,3 @@ +export default { + port: 8888, +}; diff --git a/docs/api.conf b/docs/api.conf new file mode 100644 index 0000000..4a6d4ae --- /dev/null +++ b/docs/api.conf @@ -0,0 +1,37 @@ +server { + + listen 80; + # listen 443 ssl http2; + # listen [::]:443 ssl http2; + #ssl on; + #ssl_certificate /etc/letsencrypt/live/hardcore-react-training.tunk.io/fullchain.pem; + #ssl_certificate_key /etc/letsencrypt/live/hardcore-react-training.tunk.io/privkey.pem; + + server_name api.hardcore-react-training.tunk.io; + root /home/pekkis/js/hardcore-react-training/dist; + + # Default Gzip Configuration (Set Exceptions Per Location) + gzip on; + gzip_comp_level 1; + gzip_http_version 1.1; + gzip_vary On; + gzip_proxied any; + gzip_types text/plain text/css text/xml image/svg+xml application/xml application/xml+rss application/xml+atom text/javascript application/x-javascript application/javascript application/json; + gzip_disable "MSIE [1-6]\."; + + location / { + + access_log off; + expires -1; + gzip on; + gzip_proxied any; + charset utf-8; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + proxy_set_header X-NginX-Proxy true; + proxy_pass http://127.0.0.1:8890; + proxy_redirect off; + } + +} diff --git a/docs/basic.conf b/docs/basic.conf new file mode 100644 index 0000000..b15cdf8 --- /dev/null +++ b/docs/basic.conf @@ -0,0 +1,85 @@ +server { + + listen 80; + server_name hardcore-react-training.tunk.io; + root /Users/pekkis/js/hardcore-react-training/dist; + index index.html; + + # Default Gzip Configuration (Set Exceptions Per Location) + gzip on; + gzip_comp_level 1; + gzip_http_version 1.1; + gzip_vary On; + gzip_proxied any; + gzip_types text/plain text/css text/xml image/svg+xml application/xml application/xml+rss application/xml+atom text/javascript application/x-javascript application/javascript application/json; + gzip_disable "MSIE [1-6]\."; + + # assets not worth gzipping + location ~* ^.+\.(jpg|jpeg|gif|png|ico)$ { + access_log off; + gzip off; + expires 30d; + } + + # assets worth gzipping + location ~* ^.+\.(css|js|map|html|txt)$ { + access_log off; + expires 30d; + gzip on; + charset utf-8; + } + + location = /index.html { + + #auth_basic "Get out!"; + #auth_basic_user_file $document_root/../.htpasswd; + + access_log off; + expires -1; + gzip on; + charset utf-8; + } + + + # Web fonts, must have the CORS header + location ~* ^.+\.(eot|ttf|otf|woff|woff2)$ { + access_log off; + gzip on; + expires 30d; + add_header Access-Control-Allow-Origin *; + } + + # archives + location ~* ^.+\.(pdf|gz|bz2|exe|rar|zip|7z)$ { + access_log off; + expires -1; + gzip off; + } + + # videos + location ~* ^.+\.(mp4|avi)$ { + expires -1; + access_log off; + gzip off; + } + + # evil places + location ~* (\.svn|\.git) { + access_log off; + deny all; + } + + location /api/ { + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + proxy_set_header X-NginX-Proxy true; + proxy_pass http://127.0.0.1:8888; + proxy_redirect off; + } + + location / { + try_files $uri $uri/ /index.html; + } + +} diff --git a/docs/site.conf b/docs/site.conf new file mode 100644 index 0000000..191c249 --- /dev/null +++ b/docs/site.conf @@ -0,0 +1,90 @@ +#server { +# listen 80 default_server; +# server_name diktaattoriporssi.com m.diktaattoriporssi.com; +# rewrite ^ https://m.diktaattoriporssi.com$uri permanent; +# } + +server { + + #listen 443 ssl http2; + #listen [::]:443 ssl http2; + # ssl on; + #ssl_certificate /etc/letsencrypt/live/m.diktaattoriporssi.com/fullchain.pem; + #ssl_certificate_key /etc/letsencrypt/live/m.diktaattoriporssi.com/privkey.pem; + + listen 80; + server_name hardcore-react-training.tunk.io; + root /home/pekkis/js/hardcore-react-training/dist; + + # Default Gzip Configuration (Set Exceptions Per Location) + gzip on; + gzip_comp_level 1; + gzip_http_version 1.1; + gzip_vary On; + gzip_proxied any; + gzip_types text/plain text/css text/xml image/svg+xml application/xml application/xml+rss application/xml+atom text/javascript application/x-javascript application/javascript application/json; + gzip_disable "MSIE [1-6]\."; + + # assets not worth gzipping + location ~* ^.+\.(jpg|jpeg|gif|png|ico)$ { + access_log off; + gzip off; + expires 30d; + } + + # assets worth gzipping + location ~* ^.+\.(css|js|map|html|txt)$ { + access_log off; + expires 30d; + gzip on; + charset utf-8; + } + + # Web fonts, must have the CORS header + location ~* ^.+\.(eot|ttf|otf|woff|woff2)$ { + access_log off; + gzip on; + expires 30d; + add_header Access-Control-Allow-Origin *; + } + + # archives + location ~* ^.+\.(pdf|gz|bz2|exe|rar|zip|7z)$ { + access_log off; + expires -1; + gzip off; + } + + # videos + location ~* ^.+\.(mp4|avi)$ { + expires -1; + access_log off; + gzip off; + } + + # evil places + location ~* (\.svn|\.git) { + access_log off; + deny all; + } + + location @node { + access_log off; + expires -1; + gzip on; + charset utf-8; + gzip_proxied any; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + proxy_set_header X-NginX-Proxy true; + proxy_pass http://127.0.0.1:8889; + proxy_redirect off; + } + + location / { + try_files $uri @node; + } + + +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c7bab96 --- /dev/null +++ b/package.json @@ -0,0 +1,120 @@ +{ + "name": "@dr-kobros/hardcore-react-training", + "version": "3.0.0", + "description": "An example / training project broilerplate utilizing React et al.", + "main": "src", + "scripts": { + "clean": "rimraf ./dist", + "build:client": "NODE_ENV=production webpack --progress -p", + "build:server": "NODE_ENV=production webpack --config webpack.config.server.babel.js --progress -p", + "build": "yarn run clean && yarn run build:client && yarn run build:server", + "lint": "eslint src --ext js --ext jsx", + "babel-node": "babel-node", + "start": "NODE_ENV=development ./node_modules/.bin/nodemon --exec yarn run babel-node -w src/dev.js -- src/dev.js", + "dev:server": "NODE_ENV=development ./node_modules/.bin/webpack --config=webpack.config.server.babel.js --watch --progress", + "test": "NODE_ENV=test mocha --compilers js:babel-register -r ignore-styles -r jsdom-global/register \"./src/**/*.tests.js\"", + "flow": "flow", + "serve": "nodemon -w dist/server.js dist/server.js", + "api": "NODE_ENV=development ./node_modules/.bin/nodemon --exec yarn run babel-node -w src/api.js -- src/api.js", + "storybook": "start-storybook -p 9001 -c .storybook" + }, + "repository": { + "type": "git", + "url": "https://github.com/pekkis/react-training-broilerplate" + }, + "keywords": [ + "React", + "example", + "boilerplate", + "broilerplate", + "training" + ], + "author": "Pekkis", + "license": "BSD-3-Clause", + "dependencies": { + "axios": "^0.15.2", + "body-parser": "^1.15.2", + "classnames": "^2.2.5", + "cors": "^2.8.1", + "express": "^4.14.0", + "immutable": "^3.8.1", + "merge": "^1.2.0", + "node-uuid": "^1.4.7", + "normalize.css": "^5.0.0", + "ramda": "^0.22.1", + "react": "^15.3.2", + "react-burger-menu": "^1.10.8", + "react-dom": "^15.3.2", + "react-fa": "^4.1.2", + "react-gravatar": "^2.5.2", + "react-helmet": "^3.1.0", + "react-hot-loader": "^3.0.0-beta.6", + "react-imgix": "^5.0.0", + "react-immutable-proptypes": "^2.1.0", + "react-intl": "^2.1.5", + "react-redux": "^4.4.5", + "react-router": "^3.0.0", + "react-router-redux": "^4.0.6", + "recompose": "^0.20.2", + "redbox-react": "^1.3.2", + "redial": "^0.5.0", + "redux": "^3.6.0", + "redux-promise-middleware": "^4.1.0", + "redux-thunk": "^2.1.0", + "remarkable": "^1.7.1", + "webfontloader": "^1.6.26" + }, + "devDependencies": { + "@dr-kobros/html-document-webpack-plugin": "^0.4.0", + "@kadira/storybook": "^2.26.0", + "autoprefixer": "^6.5.1", + "aws-sdk": "^2.6.12", + "babel-cli": "^6.18.0", + "babel-eslint": "^7.0.0", + "babel-loader": "^6.2.5", + "babel-preset-react-app": "^1.0.0", + "bluebird": "^3.4.6", + "chai": "^3.5.0", + "copy-webpack-plugin": "^4.0.0", + "css-loader": "^0.25.0", + "empty": "^0.10.1", + "enzyme": "^2.5.1", + "eslint": "^3.8.1", + "eslint-config-airbnb": "^12.0.0", + "eslint-plugin-flowtype": "^2.25.0", + "eslint-plugin-import": "^2.0.1", + "eslint-plugin-jsx-a11y": "^2.2.3", + "eslint-plugin-react": "^6.4.1", + "express-http-proxy": "^0.10.1", + "extract-text-webpack-plugin": "^1.0.1", + "faker": "^3.1.0", + "file-loader": "^0.9.0", + "flow-bin": "^0.34.0", + "flow-typed": "^2.0.0", + "html-webpack-plugin": "^2.24.0", + "img-loader": "^1.3.1", + "jsdom": "^9.8.3", + "jsdom-global": "^2.1.0", + "json-loader": "^0.5.4", + "mocha": "^3.1.2", + "nodemon": "^1.11.0", + "null-loader": "^0.1.1", + "postcss": "^5.2.5", + "postcss-loader": "^1.0.0", + "precss": "^1.4.0", + "random-js": "^1.0.8", + "react-addons-test-utils": "^15.3.2", + "react-html-document": "^3.1.0", + "react-inspector": "^1.1.0", + "redux-logger": "^2.7.0", + "rimraf": "^2.5.4", + "sinon": "^1.17.6", + "style-loader": "^0.13.1", + "to-markdown": "^3.0.3", + "url-loader": "^0.5.7", + "webpack": "^1.13.3", + "webpack-assets-manifest": "^0.5.0", + "webpack-dev-middleware": "^1.8.4", + "webpack-hot-middleware": "^2.13.0" + } +} diff --git a/src/HtmlDocument.js b/src/HtmlDocument.js new file mode 100644 index 0000000..371a268 --- /dev/null +++ b/src/HtmlDocument.js @@ -0,0 +1,34 @@ +import React from 'react'; + +const HtmlDocument = props => { + const { head, app, manifest, state, favicon, css, webfonts } = props; + const attrs = head.htmlAttributes.toComponent(); + const innerHTML = { __html: JSON.stringify(state) }; + return ( + + + {head && head.title.toComponent()} + {head && head.meta.toComponent()} + {head && head.link.toComponent()} + {head && head.script.toComponent()} + {css.map((css, key) => )} + + + + +
+