diff --git a/.travis.yml b/.travis.yml index e868f8d..8a28b37 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,14 @@ language: node_js node_js: - - "4.1" - - "4.0" + - "4" sudo: false +cache: + directories: + - node_modules + before_script: - export DISPLAY=:99.0 - sh -e /etc/init.d/xvfb start diff --git a/api/api.js b/api/api.js index eeed5ee..ccdcde1 100644 --- a/api/api.js +++ b/api/api.js @@ -3,6 +3,8 @@ require('../server.babel'); // babel registration (runtime transpilation for nod import express from 'express'; import bodyParser from 'body-parser'; import config from './config'; +import users from './users'; +import products from './products'; import PrettyError from 'pretty-error'; import Thinky from 'thinky'; @@ -14,13 +16,35 @@ const app = express(); app.use(bodyParser.json()); +app.get('/login', users.login); + +app.route('/users') + .get(users.getUsers) + .post(users.addUser); + +app.route('/users/:id') + .get(users.getUser) + .put(users.updateUser) + .delete(users.deleteUser); + +app.route('/products') + .get(products.getProducts) + .post(products.addProduct); + +app.route('/products/:id') + .get(products.getProduct) + .put(products.updateProduct) + .delete(products.deleteProduct); + +app.get('/search/:text', products.search); + if (config.apiPort) { app.listen(config.apiPort, (err) => { if (err) { console.error(err); } console.info('----\n==> 🌎 API is running on port %s', config.apiPort); - console.info('==> 💻 Send requests to http://localhost:%s', config.apiPort); + console.info('==> 💻 Send requests to http://localhost:%s ', config.apiPort); }); } else { console.error('==> ERROR: No PORT environment variable has been specified'); diff --git a/api/products.js b/api/products.js new file mode 100644 index 0000000..c87f5bb --- /dev/null +++ b/api/products.js @@ -0,0 +1,34 @@ +function getProducts(req, res) { + +} + +function getProduct(req, res) { + +} + +function addProduct(req, res) { + +} + +function updateProduct(req, res) { + +} + +function deleteProduct(req, res) { + +} + +function search(req, res) { + +} + +const products = { + getProducts: getProducts, + getProduct: getProduct, + addProduct: addProduct, + updateProduct: updateProduct, + deleteProduct: deleteProduct, + search: search +}; + +export default products; \ No newline at end of file diff --git a/api/users.js b/api/users.js new file mode 100644 index 0000000..6146a06 --- /dev/null +++ b/api/users.js @@ -0,0 +1,32 @@ +function login(req, res) { + console.log(req); + res.send({name: 'kant', token: 'bite'}); +} + +function getUsers(req, res) { +} + +function getUser(req, res) { +} + +function addUser(req, res) { + console.log(req); + res.send({name: 'kant', token: 'bite'}); +} + +function updateUser(req, res) { +} + +function deleteUser(req, res) { +} + +const users = { + login: login, + getUsers: getUsers, + getUser: getUser, + addUser: addUser, + updateUser: updateUser, + deleteUser: deleteUser +}; + +export default users; \ No newline at end of file diff --git a/package.json b/package.json index a860a24..03e758c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "author": "Quentin 'Kant' Jaccarino", "name": "whatashop", - "version": "0.1.0", + "version": "0.1.1", "description": "Online Shopping Application", "homepage": "https://github.com/Justkant/WhatAShop#readme", "license": "MIT", @@ -28,6 +28,7 @@ "start-prod": "node ./node_modules/better-npm-run start-prod", "start-prod-api": "node ./node_modules/better-npm-run start-prod-api", "build": "webpack --verbose --colors --display-error-details --config webpack/prod.config.js", + "postinstall": "webpack --display-error-details --config webpack/prod.config.js", "lint": "eslint -c .eslintrc src", "start-dev": "node ./node_modules/better-npm-run start-dev", "start-dev-api": "node ./node_modules/better-npm-run start-dev-api", @@ -41,6 +42,7 @@ "env": { "NODE_PATH": "./src", "NODE_ENV": "production", + "PORT": 3000, "APIPORT": 3030 } }, @@ -89,29 +91,29 @@ "body-parser": "1.14.1", "express": "4.13.3", "file-loader": "0.8.4", - "history": "1.11.1", + "history": "1.12.2", "http-proxy": "1.11.2", - "jsonwebtoken": "^5.0.5", + "jsonwebtoken": "^5.4.0", "lru-memoize": "1.0.0", "map-props": "1.0.0", - "multireducer": "1.0.0", + "multireducer": "1.0.1", "piping": "0.3.0", "pretty-error": "1.2.0", - "query-string": "2.4.1", - "react": "^0.14.0-rc1", + "query-string": "2.4.2", + "react": "^0.14.0", "react-document-meta": "2.0.0-rc2", - "react-dom": "0.14.0-rc1", - "react-redux": "3.0.1", + "react-dom": "0.14.0", + "react-redux": "3.1.0", "react-router": "1.0.0-rc1", "redux": "3.0.2", - "redux-form": "2.0.0", + "redux-form": "2.1.0", "serialize-javascript": "1.1.2", "serve-favicon": "2.3.0", "serve-static": "1.10.0", "superagent": "1.4.0", "thinky": "^2.1.8", "url-loader": "0.5.6", - "webpack-isomorphic-tools": "0.9.0" + "webpack-isomorphic-tools": "0.9.1" }, "devDependencies": { "autoprefixer-stylus": "0.8.0", @@ -125,14 +127,14 @@ "clean-webpack-plugin": "0.1.3", "concurrently": "0.1.1", "css-loader": "0.19.0", - "eslint": "1.5.1", + "eslint": "1.6.0", "eslint-config-airbnb": "0.1.0", "eslint-loader": "1.0.0", - "eslint-plugin-react": "3.5.0", - "extract-text-webpack-plugin": "0.8.2", + "eslint-plugin-react": "3.5.1", + "extract-text-webpack-plugin": "^0.8.2", "json-loader": "0.5.3", "karma": "0.13.10", - "karma-chrome-launcher": "0.2.0", + "karma-chrome-launcher": "0.2.1", "karma-cli": "0.1.1", "karma-firefox-launcher": "0.1.6", "karma-mocha": "0.2.0", @@ -141,17 +143,18 @@ "karma-webpack": "1.7.0", "mocha": "2.3.3", "react-a11y": "0.2.6", - "react-addons-test-utils": "0.14.0-rc1", + "react-addons-test-utils": "0.14.0", "react-hot-loader": "1.3.0", "react-transform-hmr": "1.0.1", "redux-devtools": "2.1.5", + "strip-loader": "^0.1.0", "style-loader": "0.12.4", "stylus-loader": "1.3.1", "webpack": "1.12.2", "webpack-dev-middleware": "1.2.0", - "webpack-hot-middleware": "2.3.0" + "webpack-hot-middleware": "2.4.1" }, "engines": { - "node": "4.0.0" + "node": "4.1.2" } } diff --git a/src/client.js b/src/client.js index 2de7f4d..eac1b4b 100644 --- a/src/client.js +++ b/src/client.js @@ -4,8 +4,7 @@ import 'babel/polyfill'; import React from 'react'; import ReactDOM from 'react-dom'; -import createHistory from 'history/lib/createBrowserHistory'; -import createLocation from 'history/lib/createLocation'; +import { createHistory } from 'history'; import createStore from './redux/create'; import ApiClient from './helpers/ApiClient'; import universalRouter from './helpers/universalRouter'; @@ -16,7 +15,7 @@ const client = new ApiClient(); const dest = document.getElementById('content'); const store = createStore(client, window.__data); -const location = createLocation(document.location.pathname, document.location.search); +const location = history.createLocation(document.location.pathname, document.location.search); const render = (loc, hist, str, preload) => { return universalRouter(loc, hist, str, preload) @@ -44,13 +43,12 @@ history.listenBefore((loc, callback) => { .then((callback)); }); -render(location, history, store); +render(location, history, store, !dest.firstChild); if (process.env.NODE_ENV !== 'production') { window.React = React; // enable debugger - const reactRoot = window.document.getElementById('content'); - if (!reactRoot || !reactRoot.firstChild || !reactRoot.firstChild.attributes || !reactRoot.firstChild.attributes['data-react-checksum']) { + if (!dest || !dest.firstChild || !dest.firstChild.attributes || !dest.firstChild.attributes['data-react-checksum']) { console.error('Server-side React render was discarded. Make sure that your initial render does not contain any client-side code.'); } } diff --git a/src/containers/Login/Login.styl b/src/components/Header/Header.js similarity index 100% rename from src/containers/Login/Login.styl rename to src/components/Header/Header.js diff --git a/src/components/Header/Header.styl b/src/components/Header/Header.styl new file mode 100644 index 0000000..e69de29 diff --git a/src/components/Search/Search.js b/src/components/Search/Search.js index 72ce4bb..d28ba35 100644 --- a/src/components/Search/Search.js +++ b/src/components/Search/Search.js @@ -1,5 +1,5 @@ import React, {Component, PropTypes} from 'react'; -import {bindActionCreators} from 'redux'; +// import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; @connect( diff --git a/src/components/__tests__/Search-test.js b/src/components/__tests__/Search-test.js index e3fb10b..0b20997 100644 --- a/src/components/__tests__/Search-test.js +++ b/src/components/__tests__/Search-test.js @@ -1,5 +1,4 @@ import React from 'react'; -import ReactDOM from 'react-dom'; import {renderIntoDocument} from 'react-addons-test-utils'; import { expect} from 'chai'; import { Search } from 'components'; diff --git a/src/containers/App/App.js b/src/containers/App/App.js index 0e071f4..42e7abd 100755 --- a/src/containers/App/App.js +++ b/src/containers/App/App.js @@ -1,9 +1,10 @@ import React, { Component, PropTypes } from 'react'; -import { Link } from 'react-router'; +import DocumentMeta from 'react-document-meta'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; -import DocumentMeta from 'react-document-meta'; -import { isLoaded as isAuthLoaded, load as loadAuth, logout } from 'redux/modules/auth'; +import { isLoaded as isAuthLoaded, load as loadAuth } from 'redux/modules/auth'; +import { Market, SignupLogin } from '../index'; +import './App.styl'; const title = 'WhatAShop'; const description = 'WhatAShop, an online shopping website.'; @@ -26,12 +27,11 @@ const meta = { @connect( state => ({user: state.auth.user}), - dispatch => bindActionCreators({logout}, dispatch)) + dispatch => bindActionCreators({loadAuth}, dispatch)) export default class App extends Component { static propTypes = { - children: PropTypes.object.isRequired, + children: PropTypes.object, user: PropTypes.object, - logout: PropTypes.func.isRequired, history: PropTypes.object }; @@ -39,7 +39,7 @@ export default class App extends Component { store: PropTypes.object.isRequired }; - componentWillReceiveProps(nextProps) { + /* componentWillReceiveProps(nextProps) { if (!this.props.user && nextProps.user) { // login this.props.history.pushState(null, '/'); @@ -50,7 +50,7 @@ export default class App extends Component { // console.log(this.props.user); // console.log(nextProps.user); } - } + } */ static fetchData(store) { const promises = []; @@ -60,53 +60,13 @@ export default class App extends Component { return Promise.all(promises); } - handleLogout(event) { - event.preventDefault(); - this.props.logout(); - } - render() { const {user} = this.props; - const styles = require('./App.styl'); return ( -
+
- -
- {this.props.children} -
- -
- Have questions? Ask for help on Github or in the #react-redux-universal Slack channel. -
+ {!user && } + {user && }
); } diff --git a/src/containers/App/App.styl b/src/containers/App/App.styl index e69de29..446b4ca 100644 --- a/src/containers/App/App.styl +++ b/src/containers/App/App.styl @@ -0,0 +1,36 @@ +html, body { + font-family: Roboto,Arial,sans-serif; + height: 100%; + width: 100%; + margin: 0; + -webkit-font-smoothing: antialiased; +} + +* { + box-sizing: border-box; +} + +header { + padding-right: 5%; + padding-left: 5%; + height: 50px; + position: fixed; + z-index: 100; + right: 0; + left: 0; + top: 0; + display: flex; + flex-direction: row; + align-items: center; +} + +.headerLogo { + font-size: 1.5em; + color: white; + text-decoration: none; + cursor: pointer; +} + +.flexSpace { + flex: 1 1 auto; +} diff --git a/src/containers/Login/Login.js b/src/containers/Login/Login.js deleted file mode 100755 index 72d2e11..0000000 --- a/src/containers/Login/Login.js +++ /dev/null @@ -1,61 +0,0 @@ -import React, {Component, PropTypes} from 'react'; -import {bindActionCreators} from 'redux'; -import {connect} from 'react-redux'; -import DocumentMeta from 'react-document-meta'; -import * as authActions from 'redux/modules/auth'; -import {isLoaded as isAuthLoaded, load as loadAuth} from 'redux/modules/auth'; - -@connect( - state => ({user: state.auth.user}), - dispatch => bindActionCreators(authActions, dispatch) -) -export default class Login extends Component { - static propTypes = { - user: PropTypes.object, - login: PropTypes.func, - logout: PropTypes.func - } - - static fetchData(store) { - if (!isAuthLoaded(store.getState())) { - return store.dispatch(loadAuth()); - } - } - - handleSubmit(event) { - event.preventDefault(); - const input = this.refs.username; - this.props.login(input.value); - input.value = ''; - } - - render() { - const {user, logout} = this.props; - const styles = require('./Login.styl'); - return ( -
- -

Login

- {!user && -
-
- - - -

This will "log you in" as this user, storing the username in the session of the API server.

-
- } - {user && -
-

You are currently logged in as {user.name}.

- -
- -
-
- } -
- ); - } -} diff --git a/src/containers/Market/Market.js b/src/containers/Market/Market.js index 2f74ba5..3cf4380 100755 --- a/src/containers/Market/Market.js +++ b/src/containers/Market/Market.js @@ -1,148 +1,44 @@ -import React, { Component } from 'react'; -import { Link } from 'react-router'; -// import { CounterButton, GithubButton } from 'components'; +import React, { Component, PropTypes } from 'react'; +import { connect } from 'react-redux'; +import './Market.styl'; +@connect( + state => ({user: state.auth.user})) export default class Market extends Component { - render() { - const styles = require('./Market.styl'); - // require the logo image both from client and server - const logoImage = require('./logo.png'); - return ( -
-
-
-
-

- -

-
-

React Redux Example

+ static propTypes = { + children: PropTypes.object, + user: PropTypes.object + }; -

All the modern best practices in one example.

+ static contextTypes = { + store: PropTypes.object.isRequired + }; -

- - View on Github - -

+ render() { + /* const styles = require('./Market.styl'); + const logoImage = require('./logo.png'); */ + const {user} = this.props; -

- Created and maintained by @erikras. -

+ return ( +
+
+ + WS + +
+
+
-
- -
-

This starter boilerplate app uses the following technologies:

- - - -

Features demonstrated in this project

- -
-
Multiple components subscribing to same redux store slice
-
- The App.js that wraps all the pages contains an InfoBar component - that fetches data from the server initially, but allows for the user to refresh the data from - the client. About.js contains a MiniInfoBar that displays the same - data. -
-
Server-side data loading
-
- The Widgets page demonstrates how to fetch data asynchronously from - some source that is needed to complete the server-side rendering. Widgets.js's - fetchData() function is called from universalRouter.js before - the widgets page is loaded, on either the server or the client, allowing all the widget data - to be loaded and ready for the page to render. -
-
Data loading errors
-
- The Widgets page also demonstrates how to deal with data loading - errors in Redux. The API endpoint that delivers the widget data intentionally fails 33% of - the time to highlight this. The clientMiddleware sends an error action which - the widgets reducer picks up and saves to the Redux state for presenting to the user. -
-
Session based login
-
- On the Login page you can submit a username which will be sent to the server - and stored in the session. Subsequent refreshes will show that you are still logged in. -
-
Redirect after state change
-
- After you log in, you will be redirected to a Login Success page. This magic logic - is performed in componentWillReceiveProps() in App.js, but it could - be done in any component that listens to the appropriate store slice, via Redux's @connect, - and pulls the router from the context. -
-
Auth-required views
-
- The aforementioned Login Success page is only visible to you if you are logged in. If you try - to go there when you are not logged in, you will be forwarded back - to this home page. This magic logic is performed by the - RequireLogin component, which generates an onEnter() function given - the Redux store, and then it simply wraps any routes that need to be secured. -
-
Forms
-
- The Survey page uses the - still-experimental redux-form to - manage form state inside the Redux store. This includes immediate client-side validation. -
-
WebSockets / socket.io
-
- The Chat uses the socket.io technology for real-time - commnunication between clients. You need to login first. -
-
- -

From the author

- -

- I cobbled this together from a wide variety of similar "starter" repositories. As I post this in June 2015, - all of these libraries are right at the bleeding edge of web development. They may fall out of fashion as - quickly as they have come into it, but I personally believe that this stack is the future of web development - and will survive for several years. I'm building my new projects like this, and I recommend that you do, - too. -

- -

Thanks for taking the time to check this out.

+
+ + -

– Erik Rasmussen

-
+ {this.props.children}
); } diff --git a/src/containers/Market/Market.styl b/src/containers/Market/Market.styl index e69de29..7ab8602 100644 --- a/src/containers/Market/Market.styl +++ b/src/containers/Market/Market.styl @@ -0,0 +1,56 @@ +header.blue { + background: #3585b5; +} + +.searchBar { + flex: 2 2 auto; + border-radius: 4px; + background: white; +} + +.searchBar input { + border: 0; + width: 100%; + padding: 5px 10px 5px 13px; + display: block; + margin: 0; + border: 0; + background: 0; + font-size: 16px; +} + +.searchBar input { + outline: 0; +} + +.linkDropdown { + align-self: stretch; + display: flex; + flex-direction: column; +} + +.buttonLink { + text-decoration: none; + color: white; + flex: 1 1 auto; + display: flex; + align-items: center; + padding-left: 10px; + padding-right: 10px; +} + +.buttonLink:hover { + background: #2a76a3; +} + +.buttonLink img { + width: 32px; + height: 32px; + vertical-align:middle; + border-radius: 50%; +} + +.buttonLink span { + margin-left: 10px; + line-height: 32px; +} \ No newline at end of file diff --git a/src/containers/Product/Product.js b/src/containers/Product/Product.js new file mode 100644 index 0000000..e69de29 diff --git a/src/containers/Profile/Profile.js b/src/containers/Profile/Profile.js new file mode 100644 index 0000000..e69de29 diff --git a/src/containers/RequireLogin/RequireLogin.js b/src/containers/RequireLogin/RequireLogin.js deleted file mode 100755 index 9c14ad9..0000000 --- a/src/containers/RequireLogin/RequireLogin.js +++ /dev/null @@ -1,21 +0,0 @@ -import {Component, PropTypes} from 'react'; -import {connect} from 'react-redux'; - -@connect(state => ({user: state.auth.user})) -export default class RequireLogin extends Component { - static propTypes = { - user: PropTypes.object, - history: PropTypes.object.isRequired - }; - - componentWillMount() { - const {history, user} = this.props; - if (!user) { - history.replaceState(null, '/login'); - } - } - - render() { - return this.props.children; - } -} diff --git a/src/containers/SignupLogin/SignupLogin.js b/src/containers/SignupLogin/SignupLogin.js new file mode 100644 index 0000000..949b19a --- /dev/null +++ b/src/containers/SignupLogin/SignupLogin.js @@ -0,0 +1,57 @@ +import React, { Component } from 'react'; +import './SignupLogin.styl'; + +export default class SignupLogin extends Component { + constructor() { + super(); + this.state = { + signup: true + }; + } + + toggleSignup() { + this.setState({signup: !this.state.signup}); + } + + render() { + const {signup} = this.state; + return ( +
+
+ + WS + +
+ + {signup && 'Log in'} + {!signup && 'Sign up'} + +
+ +
+
+

What A Shop

+
+
+ +
+
+ +
+ {signup &&
+ +
} +
+ + {signup && 'Sign up'} + {!signup && 'Log in'} + +
+
+ +
+
+
+ ); + } +} diff --git a/src/containers/SignupLogin/SignupLogin.styl b/src/containers/SignupLogin/SignupLogin.styl new file mode 100644 index 0000000..b199acf --- /dev/null +++ b/src/containers/SignupLogin/SignupLogin.styl @@ -0,0 +1,100 @@ +.background { + position: fixed; + height: 100%; + width: 100%; + z-index: 1; + background: url(./bg.jpg) no-repeat center center fixed; + background-size: cover; +} + +.backgroundFader { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + height: 100%; + z-index: 2; + background-image: linear-gradient(rgba(0,0,0,0.4) 0%,rgba(0,0,0,.6) 75%,rgba(0,0,0,.8) 100%); +} + +.outlineButton { + color: white; + padding: 5px 10px; + border: 1px solid white; + border-radius: 4px; + text-decoration: none; + cursor: pointer; +} + +.outlineButton:hover { + color: black; + background-color: white; +} + +.centerSignup { + position: fixed; + height: 100%; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + z-index: 50; +} + +.signupContainer { + display: flex; + flex-direction: column; + align-items: center; +} + +.title { + color: white; + font-size: 3em; + font-weight: normal; +} + +.inputGroup { + margin-bottom: 20px; + border-radius: 4px; + background: white; + width: 100%; +} + +.inputBox { + border-top: 1px solid #e3e3e3; +} + +.inputBox:first-child { + border-top: 0; +} + +.inputBox input { + width: 100%; + padding: 11px 10px 11px 13px; + display: block; + margin: 0; + border: 0; + background: 0; + font-size: 16px; +} + +.inputBox input:focus { + outline: 0; +} + +.signupButton { + height: 45px; + line-height: 45px; + width: 100%; + background: #529ecc; + color: white; + text-align: center; + border-radius: 4px; + text-decoration: none; + cursor: pointer; +} + +.signupButton:active { + background: #3585b5; +} diff --git a/src/containers/SignupLogin/bg.jpg b/src/containers/SignupLogin/bg.jpg new file mode 100644 index 0000000..a13eb21 Binary files /dev/null and b/src/containers/SignupLogin/bg.jpg differ diff --git a/src/containers/index.js b/src/containers/index.js index f66db4c..0b7a9d6 100755 --- a/src/containers/index.js +++ b/src/containers/index.js @@ -1,5 +1,6 @@ export App from './App/App'; export Market from './Market/Market'; -export Login from './Login/Login'; -export RequireLogin from './RequireLogin/RequireLogin'; +export SignupLogin from './SignupLogin/SignupLogin'; +// export Product from './Product/Product'; +// export Profile from './Profile/Profile'; export NotFound from './NotFound/NotFound'; diff --git a/src/helpers/Html.js b/src/helpers/Html.js index a83c48f..7194f96 100644 --- a/src/helpers/Html.js +++ b/src/helpers/Html.js @@ -16,13 +16,13 @@ import DocumentMeta from 'react-document-meta'; export default class Html extends Component { static propTypes = { assets: PropTypes.object, - component: PropTypes.object, + component: PropTypes.node, store: PropTypes.object } render() { const {assets, component, store} = this.props; - const content = ReactDOM.renderToString(component); + const content = component ? ReactDOM.renderToString(component) : ''; return ( @@ -31,6 +31,7 @@ export default class Html extends Component { {DocumentMeta.renderAsReact()} + {/* styles (will be present only in production with webpack extract text plugin) */} {Object.keys(assets.styles).map((style, index) => diff --git a/src/redux/modules/auth.js b/src/redux/modules/auth.js index 66b7009..5606ae4 100644 --- a/src/redux/modules/auth.js +++ b/src/redux/modules/auth.js @@ -80,7 +80,7 @@ export function isLoaded(globalState) { export function load() { return { types: [LOAD, LOAD_SUCCESS, LOAD_FAIL], - promise: () => new Promise((resolve) => resolve({name: 'kant'})) // (client) => client.get('/loadAuth') + promise: () => new Promise((resolve) => resolve(null)) // (client) => client.get('/loadAuth') }; } diff --git a/src/routes.js b/src/routes.js index 174eb89..1827f99 100644 --- a/src/routes.js +++ b/src/routes.js @@ -2,23 +2,16 @@ import React from 'react'; import {Route} from 'react-router'; import { App, - Market, - Profile, - Product, - Login, - RequireLogin, + /* Profile, + Product, */ NotFound } from './containers'; export default function(history) { return ( - - - - - - - + + {/* + */} ); diff --git a/src/server.js b/src/server.js index be4f4e0..e020b51 100644 --- a/src/server.js +++ b/src/server.js @@ -1,7 +1,7 @@ import Express from 'express'; import React from 'react'; import ReactDOM from 'react-dom/server'; -import createLocation from 'history/lib/createLocation'; +import { createMemoryHistory } from 'history'; import config from './config'; import favicon from 'serve-favicon'; import httpProxy from 'http-proxy'; @@ -47,7 +47,7 @@ app.use((req, res) => { } const client = new ApiClient(req); const store = createStore(client); - const location = createLocation(req.path, req.query); + const location = createMemoryHistory().createLocation(req.originalUrl); universalRouter(location, undefined, store, true) .then(({component, redirectLocation}) => { @@ -65,8 +65,7 @@ app.use((req, res) => { } console.error('ROUTER ERROR:', pretty.render(error)); res.send('\n' + - ReactDOM.renderToString(
} - store={store}/>)); + ReactDOM.renderToString()); }); }); diff --git a/webpack/dev.config.js b/webpack/dev.config.js index 098401e..a2edbda 100644 --- a/webpack/dev.config.js +++ b/webpack/dev.config.js @@ -15,67 +15,67 @@ var babelrc = fs.readFileSync('./.babelrc'); var babelLoaderQuery = {}; try { - babelLoaderQuery = JSON.parse(babelrc); + babelLoaderQuery = JSON.parse(babelrc); } catch (err) { - console.error('==> ERROR: Error parsing your .babelrc.'); - console.error(err); + console.error('==> ERROR: Error parsing your .babelrc.'); + console.error(err); } babelLoaderQuery.plugins = babelLoaderQuery.plugins || []; babelLoaderQuery.plugins.push('react-transform'); babelLoaderQuery.extra = babelLoaderQuery.extra || {}; babelLoaderQuery.extra['react-transform'] = { - transforms: [{ - transform: 'react-transform-hmr', - imports: ['react'], - locals: ['module'] - }] + transforms: [{ + transform: 'react-transform-hmr', + imports: ['react'], + locals: ['module'] + }] }; module.exports = { - devtool: 'inline-source-map', - context: path.resolve(__dirname, '..'), - entry: { - 'main': [ - 'webpack-hot-middleware/client?path=http://' + host + ':' + port + '/__webpack_hmr', - './src/client.js' - ] - }, - output: { - path: assetsPath, - filename: '[name]-[hash].js', - chunkFilename: '[name]-[chunkhash].js', - publicPath: 'http://' + host + ':' + port + '/dist/' - }, - module: { - loaders: [ - { test: /\.js$/, exclude: /node_modules/, loaders: ['babel?' + JSON.stringify(babelLoaderQuery), 'eslint-loader']}, - { test: /\.json$/, loader: 'json-loader' }, - { test: /\.styl$/, loader: 'style-loader!css-loader!stylus-loader' }, - { test: webpackIsomorphicToolsPlugin.regular_expression('images'), loader: 'url-loader?limit=10240' } - ] - }, - stylus: { - use: [autoprefixer()] - }, - progress: true, - resolve: { - modulesDirectories: [ - 'src', - 'node_modules' - ], - extensions: ['', '.json', '.js'] - }, - plugins: [ - // hot reload - new webpack.HotModuleReplacementPlugin(), - new webpack.IgnorePlugin(/webpack-stats\.json$/), - new webpack.DefinePlugin({ - __CLIENT__: true, - __SERVER__: false, - __DEVELOPMENT__: true, - __DEVTOOLS__: false // <-------- DISABLE redux-devtools HERE - }), - webpackIsomorphicToolsPlugin.development() + devtool: 'inline-source-map', + context: path.resolve(__dirname, '..'), + entry: { + 'main': [ + 'webpack-hot-middleware/client?path=http://' + host + ':' + port + '/__webpack_hmr', + './src/client.js' ] + }, + output: { + path: assetsPath, + filename: '[name]-[hash].js', + chunkFilename: '[name]-[chunkhash].js', + publicPath: 'http://' + host + ':' + port + '/dist/' + }, + module: { + loaders: [ + {test: /\.js$/, exclude: /node_modules/, loaders: ['babel?' + JSON.stringify(babelLoaderQuery), 'eslint-loader']}, + {test: /\.json$/, loader: 'json-loader'}, + {test: /\.styl$/, loader: 'style-loader!css-loader!stylus-loader'}, + {test: webpackIsomorphicToolsPlugin.regular_expression('images'), loader: 'url-loader?limit=10240'} + ] + }, + stylus: { + use: [autoprefixer()] + }, + progress: true, + resolve: { + modulesDirectories: [ + 'src', + 'node_modules' + ], + extensions: ['', '.json', '.js'] + }, + plugins: [ + // hot reload + new webpack.HotModuleReplacementPlugin(), + new webpack.IgnorePlugin(/webpack-stats\.json$/), + new webpack.DefinePlugin({ + __CLIENT__: true, + __SERVER__: false, + __DEVELOPMENT__: true, + __DEVTOOLS__: false // <-------- DISABLE redux-devtools HERE + }), + webpackIsomorphicToolsPlugin.development() + ] }; diff --git a/webpack/prod.config.js b/webpack/prod.config.js index 3cfbbea..a956663 100644 --- a/webpack/prod.config.js +++ b/webpack/prod.config.js @@ -15,68 +15,67 @@ var WebpackIsomorphicToolsPlugin = require('webpack-isomorphic-tools/plugin'); var webpackIsomorphicToolsPlugin = new WebpackIsomorphicToolsPlugin(require('./webpack-isomorphic-tools')); module.exports = { - devtool: 'source-map', - context: path.resolve(__dirname, '..'), - entry: { - 'main': './src/client.js' - }, - output: { - path: assetsPath, - filename: '[name]-[chunkhash].js', - chunkFilename: '[name]-[chunkhash].js', - publicPath: '/dist/' - }, - module: { - loaders: [ - { test: /\.js$/, exclude: /node_modules/, loaders: [strip.loader('debug'), 'babel']}, - { test: /\.json$/, loader: 'json-loader' }, - { test: /\.styl$/, loader: 'style-loader!css-loader!stylus-loader' }, - { test: webpackIsomorphicToolsPlugin.regular_expression('images'), loader: 'url-loader?limit=10240' } - ] - }, - stylus: { - use: [autoprefixer()] - }, - progress: true, - resolve: { - modulesDirectories: [ - 'src', - 'node_modules' - ], - extensions: ['', '.json', '.js'] - }, - plugins: [ - new CleanPlugin([relativeAssetsPath]), + devtool: 'source-map', + context: path.resolve(__dirname, '..'), + entry: { + 'main': './src/client.js' + }, + output: { + path: assetsPath, + filename: '[name]-[chunkhash].js', + chunkFilename: '[name]-[chunkhash].js', + publicPath: '/dist/' + }, + module: { + loaders: [ + {test: /\.js$/, exclude: /node_modules/, loaders: [strip.loader('console.log', 'debug'), 'babel']}, + {test: /\.json$/, loader: 'json-loader'}, + {test: /\.styl$/, loader: ExtractTextPlugin.extract('style', 'css-loader!stylus-loader') }, + {test: webpackIsomorphicToolsPlugin.regular_expression('images'), loader: 'url-loader?limit=10240'} + ] + }, + stylus: { + use: [autoprefixer()] + }, + progress: true, + resolve: { + modulesDirectories: [ + 'src', + 'node_modules' + ], + extensions: ['', '.json', '.js'] + }, + plugins: [ + new CleanPlugin([relativeAssetsPath]), - // css files from the extract-text-plugin loader - new ExtractTextPlugin('[name]-[chunkhash].css', {allChunks: true}), - new webpack.DefinePlugin({ - __CLIENT__: true, - __SERVER__: false, - __DEVELOPMENT__: false, - __DEVTOOLS__: false - }), + new ExtractTextPlugin('[name]-[chunkhash].css', {allChunks: true}), + new webpack.DefinePlugin({ + __CLIENT__: true, + __SERVER__: false, + __DEVELOPMENT__: false, + __DEVTOOLS__: false + }), - // ignore dev config - new webpack.IgnorePlugin(/\.\/dev/, /\/config$/), + // ignore dev config + new webpack.IgnorePlugin(/\.\/dev/, /\/config$/), - // set global vars - new webpack.DefinePlugin({ - 'process.env': { - // Useful to reduce the size of client-side libraries, e.g. react - NODE_ENV: JSON.stringify('production') - } - }), + // set global vars + new webpack.DefinePlugin({ + 'process.env': { + // Useful to reduce the size of client-side libraries, e.g. react + NODE_ENV: JSON.stringify('production') + } + }), - // optimizations - new webpack.optimize.DedupePlugin(), - new webpack.optimize.OccurenceOrderPlugin(), - new webpack.optimize.UglifyJsPlugin({ - compress: { - warnings: false - } - }), + // optimizations + new webpack.optimize.DedupePlugin(), + new webpack.optimize.OccurenceOrderPlugin(), + new webpack.optimize.UglifyJsPlugin({ + compress: { + warnings: false + } + }), - webpackIsomorphicToolsPlugin - ] + webpackIsomorphicToolsPlugin + ] }; diff --git a/webpack/webpack-isomorphic-tools.js b/webpack/webpack-isomorphic-tools.js index 9d9cb65..d16df03 100644 --- a/webpack/webpack-isomorphic-tools.js +++ b/webpack/webpack-isomorphic-tools.js @@ -3,18 +3,44 @@ var WebpackIsomorphicToolsPlugin = require('webpack-isomorphic-tools/plugin'); // see this link for more info on what all of this means // https://github.com/halt-hammerzeit/webpack-isomorphic-tools module.exports = { - webpack_assets_file_path: 'webpack-stats.json', + webpack_assets_file_path: 'webpack-stats.json', - assets: { - images: { - extensions: [ - 'jpeg', - 'jpg', - 'png', - 'gif', - 'svg' - ], - parser: WebpackIsomorphicToolsPlugin.url_loader_parser + assets: { + images: { + extensions: [ + 'jpeg', + 'jpg', + 'png', + 'gif', + 'svg' + ], + parser: WebpackIsomorphicToolsPlugin.url_loader_parser + }, + style_modules: { + extensions: ['styl'], + filter: function (m, regex) { + return regex.test(m.name); + }, + naming: function (m, options, log) { + //find index of '/src' inside the module name, slice it and resolve path + var srcIndex = m.name.indexOf('/src'); + var name = '.' + m.name.slice(srcIndex); + if (name) { + // Resolve the e.g.: "C:\" issue on windows + const i = name.indexOf(':'); + if (i >= 0) { + name = name.slice(i + 1); + } } + return name; + }, + parser: function (m, options, log) { + if (m.source) { + var regex = options.development ? /exports\.locals = ((.|\n)+);/ : /module\.exports = ((.|\n)+);/; + var match = m.source.match(regex); + return match ? JSON.parse(match[1]) : {}; + } + } } + } }; \ No newline at end of file