From 1477c3012ed5b1df56ca6b1be909132c492fbd33 Mon Sep 17 00:00:00 2001 From: Leonardo Gentile Date: Fri, 16 Jun 2017 17:48:14 +0200 Subject: [PATCH] Big refactoring to adapt it to the new packages --- .gitignore | 2 + .idea/encodings.xml | 6 + .idea/inspectionProfiles/Project_Default.xml | 25 ++ .idea/misc.xml | 6 + .idea/modules.xml | 8 + .idea/react-mobx-router5-example.iml | 12 + .idea/watcherTasks.xml | 4 + package.json | 58 +++-- src/actions/auth.js | 58 ++--- src/app.jsx | 17 +- src/components/Home/Home.jsx | 51 ++++ src/components/Home/index.js | 1 + src/{pages => components/Index}/Index.jsx | 46 ++-- src/components/Index/Index.sass | 0 src/components/Index/index.js | 1 + src/components/Layout/Footer/Footer.jsx | 39 ++- src/components/Layout/Footer/Footer.sass | 10 + src/components/Layout/Header/Header.jsx | 44 ++-- src/components/Layout/Header/Header.sass | 45 ++-- .../Layout/Header/NavAccount/NavAccount.jsx | 70 ------ .../Layout/Header/NavAccount/NavAccount.sass | 42 ---- .../Layout/Header/NavMenu/NavMenu.jsx | 80 ++++++ .../Layout/Header/NavMenu/NavMenu.sass | 18 ++ .../Layout/Header/img/home_logo.jpg | Bin 13732 -> 0 bytes src/components/Layout/Layout.jsx | 19 +- src/components/Layout/Layout.sass | 21 +- src/components/Layout/Sidebar/Sidebar.jsx | 14 -- src/components/Login/Login.jsx | 59 +++++ src/components/Login/Login.sass | 3 + src/components/Login/index.js | 1 + src/components/Main/Main.jsx | 43 ++-- src/components/Sections/Sections.jsx | 23 ++ src/components/SubSections/SubSections.jsx | 24 ++ src/config.js | 2 +- src/create-router5.js | 28 +-- src/pages/Home.jsx | 17 -- src/pages/Home.sass | 10 - src/pages/Index.sass | 10 - src/pages/Login.jsx | 95 ------- src/routes.js | 25 +- src/stores/RouterStore.js | 7 + src/stores/TabStore.js | 2 +- src/stores/UserStore.js | 13 - src/stores/index.js | 13 +- src/styles/abstracts/_mixins.sass | 43 +++- src/styles/abstracts/_variables.sass | 32 +-- src/styles/base/_commons.sass | 16 +- webpack.config.js | 64 ++--- webpack.loaders.js | 231 +++++++++++++++--- webpack.production.config.js | 2 +- 50 files changed, 876 insertions(+), 584 deletions(-) create mode 100644 .idea/encodings.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/react-mobx-router5-example.iml create mode 100644 .idea/watcherTasks.xml create mode 100644 src/components/Home/Home.jsx create mode 100644 src/components/Home/index.js rename src/{pages => components/Index}/Index.jsx (54%) create mode 100644 src/components/Index/Index.sass create mode 100644 src/components/Index/index.js delete mode 100644 src/components/Layout/Header/NavAccount/NavAccount.jsx delete mode 100644 src/components/Layout/Header/NavAccount/NavAccount.sass create mode 100644 src/components/Layout/Header/NavMenu/NavMenu.jsx create mode 100644 src/components/Layout/Header/NavMenu/NavMenu.sass delete mode 100644 src/components/Layout/Header/img/home_logo.jpg delete mode 100644 src/components/Layout/Sidebar/Sidebar.jsx create mode 100644 src/components/Login/Login.jsx create mode 100644 src/components/Login/Login.sass create mode 100644 src/components/Login/index.js create mode 100644 src/components/Sections/Sections.jsx create mode 100644 src/components/SubSections/SubSections.jsx delete mode 100644 src/pages/Home.jsx delete mode 100644 src/pages/Home.sass delete mode 100644 src/pages/Index.sass delete mode 100644 src/pages/Login.jsx create mode 100644 src/stores/RouterStore.js diff --git a/.gitignore b/.gitignore index 17628a8..d2e0995 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ public # Ignore my .mertrc file .mertrc +# Sublime projects +*.sublime* diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..97626ba --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..a10ff03 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,25 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..24eb271 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..6009b0f --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/react-mobx-router5-example.iml b/.idea/react-mobx-router5-example.iml new file mode 100644 index 0000000..24643cc --- /dev/null +++ b/.idea/react-mobx-router5-example.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/watcherTasks.xml b/.idea/watcherTasks.xml new file mode 100644 index 0000000..9d98b43 --- /dev/null +++ b/.idea/watcherTasks.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/package.json b/package.json index 46be48b..f99dd9e 100644 --- a/package.json +++ b/package.json @@ -1,64 +1,62 @@ { - "name": "react-webpack-babel", - "version": "0.0.3", - "description": "React Webpack Babel Starter Kit", + "name": "react-mobx-router5-example", + "version": "0.0.1", + "description": "Example of React Integration with MobX and Router5", "main": "''", - "scripts": { - "build": "webpack --config webpack.production.config.js --progress --profile --colors", - "start": "webpack-dev-server --progress --profile", - "lint": "eslint --ext js --ext jsx src || exit 0", - "dev": "webpack-dashboard -- webpack-dev-server --progress --profile --colors" - }, "repository": { "type": "git", - "url": "https://github.com/LeonardoGentile/react-webpack-babel-simple-starter" + "url": "https://github.com/LeonardoGentile/react-mobx-router5-example" }, "author": "Leonardo Gentile", "license": "MIT", - "homepage": "https://github.com/LeonardoGentile/react-webpack-babel-simple-starter#readme", + "homepage": "https://github.com/LeonardoGentile/react-mobx-router5-example#readme", "dependencies": { - "axios": "^0.15.3", - "classnames": "^2.2.5", - "js-cookie": "^2.1.3", + "axios": "0.16.2", "mobx": "3.1.0", "mobx-react": "^4.1.0", + "mobx-router5": "^2.0.1", "react": "15.4.1", "react-dom": "15.4.1", - "react-router5": "^4.0.1", + "react-mobx-router5": "^2.0.3", "router5": "^4.5.1" }, "devDependencies": { "autoprefixer": "^6.7.2", - "babel-core": "6.17.0", + "babel-core": "6.23.1", "babel-eslint": "^6.1.2", - "babel-loader": "6.2.7", + "babel-loader": "6.3.2", "babel-plugin-react-html-attrs": "^2.0.0", "babel-plugin-transform-class-properties": "^6.16.0", "babel-plugin-transform-decorators-legacy": "^1.3.4", "babel-plugin-transform-runtime": "^6.15.0", - "babel-polyfill": "^6.16.0", - "babel-preset-es2015": "6.16.0", - "babel-preset-react": "6.16.0", + "babel-preset-es2015": "6.22.0", + "babel-preset-react": "6.23.0", "babel-preset-stage-3": "^6.22.0", - "babel-runtime": "^6.11.6", + "babel-runtime": "^6.22.0", "css-loader": "0.26.0", "eslint": "^3.16.0", "eslint-plugin-react": "^6.10.0", - "extract-text-webpack-plugin": "^1.0.1", - "file-loader": "0.9.0", - "html-webpack-plugin": "^2.22.0", - "lost": "^8.0.0", + "extract-text-webpack-plugin": "^2.0.1", + "file-loader": "0.10.0", + "html-webpack-plugin": "^2.26.0", "mobx-react-devtools": "^4.2.10", - "node-sass": "^3.10.1", + "node-sass": "^4.0.0", "postcss-loader": "^1.3.0", "react-hot-loader": "^3.0.0-beta.6", - "sass-loader": "^4.0.2", + "sass-loader": "^6.0.2", "sass-resources-loader": "^1.2.0", + "source-map-loader": "^0.2.1", "style-loader": "0.13.1", "url-loader": "0.5.7", - "webpack": "1.13.2", - "webpack-cleanup-plugin": "^0.4.1", + "webpack": "2.3.2", + "webpack-cleanup-plugin": "^0.4.2", "webpack-dashboard": "^0.3.0", - "webpack-dev-server": "1.16.3" + "webpack-dev-server": "2.4.2" + }, + "scripts": { + "build": "webpack --config webpack.production.config.js --progress --profile --colors", + "start": "webpack-dev-server --progress --profile", + "lint": "eslint --ext js --ext jsx src || exit 0", + "dev": "webpack-dashboard -- webpack-dev-server --progress --profile --colors" } } diff --git a/src/actions/auth.js b/src/actions/auth.js index 317a1d0..061fb33 100644 --- a/src/actions/auth.js +++ b/src/actions/auth.js @@ -1,18 +1,16 @@ import axios from 'axios'; -import Cookies from 'js-cookie'; import querystring from 'querystring'; import userStore from '../stores/UserStore' // TODO: function _handleErrors(response) { if (response.data && response.data.success !== true) { - throw Error(response); + throw Error(response); } return response; } - -export function login(formData, callback=undefined){ +export function login(formData, callback = undefined) { axios({ method: 'post', url: '/login', @@ -21,33 +19,37 @@ export function login(formData, callback=undefined){ }, data: querystring.stringify(formData) }) - .then(_handleErrors) - .then((response) => { - console.log(response); - userStore.setUser(response.data.data); - }) - .catch((error) => { - // optional callback - typeof callback == "function" && callback(error); - // TODO: _handleErrors(error, callback) - console.log(error); - }); + .then(_handleErrors) + .then((response) => { + console.log(response); + userStore.setUser(response.data.data); + }) + .catch((error) => { + // optional callback + typeof callback === "function" && callback(error); + // TODO: _handleErrors(error, callback) + console.log(error); + }); } - - -export function logout(){ +export function logout() { axios.get("/logout") - .then((res) => { + .then((res) => { + // or setUser(res.data.data) basically the same + userStore.resetUser(); + }) +} - // TODO: - // - Remove cookies (also disclaimer) - // - Remove stored tabs - // - remove last finder link - // - remove watchlist - // - go to home page +export function fakeLogin() { + const user = { + "level": "M", + "status": "A", + "name": "Leo", + "job": "Buttons Pusher" + }; + userStore.setUser(user); +} - // or setUser(res.data.data) basically the same - userStore.resetUser(); - }) +export function fakeLogout() { + userStore.resetUser(); } diff --git a/src/app.jsx b/src/app.jsx index fe2f8a9..ddc39fa 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -1,28 +1,23 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { Provider } from 'mobx-react'; -import { RouterProvider } from 'react-router5'; -import Layout from './components/Layout' +import {Provider} from 'mobx-react'; // Local imports -import * as config from './config'; +import config from './config'; import * as stores from './stores'; // mobx stores import createRouter from './create-router5' // Router5 +import Layout from './components/Layout' -// ================= -// = Global Styles = -// ================= +// Global Styles import './styles/base/_reset.sass'; import './styles/base/_commons.sass'; const router = createRouter(true); -// RouterProvider will add your router instance in context. +// Provider will add your router instance in context. const wrappedApp = ( - - - + ); diff --git a/src/components/Home/Home.jsx b/src/components/Home/Home.jsx new file mode 100644 index 0000000..a60a232 --- /dev/null +++ b/src/components/Home/Home.jsx @@ -0,0 +1,51 @@ +import React from 'react'; +import {inject} from 'mobx-react'; +import {BaseLink} from "react-mobx-router5"; + +@inject('routerStore') +class Home extends React.Component { + constructor(props){ + super(props); + this.onClick = this.onClick.bind(this); + } + + onClick(e) { + e.preventDefault(); + e.stopPropagation(); + console.log("Someone pushed me"); + } + + render() { + return ( +
+

Home Page

+
+

Examples of the different uses of BaseLink.
+ Remember that BaseLink does not re-render on route changes and so it's not aware when it is `active`.

+
+ + + BaseLink using the routerStore for computing the link --> /index/1 +

+ + + BaseLink using the router5 instance for computing the link --> /index/2 +

+ + + BaseLink using the `onClick` callback prop, it doesn't need router, routerStore and routeName props --> prints to console +
+ +
+ ); + } +} + +export default Home; diff --git a/src/components/Home/index.js b/src/components/Home/index.js new file mode 100644 index 0000000..17fb24f --- /dev/null +++ b/src/components/Home/index.js @@ -0,0 +1 @@ +export { default as Home}from "./Home"; diff --git a/src/pages/Index.jsx b/src/components/Index/Index.jsx similarity index 54% rename from src/pages/Index.jsx rename to src/components/Index/Index.jsx index 2fa7fa6..9bfa480 100644 --- a/src/pages/Index.jsx +++ b/src/components/Index/Index.jsx @@ -1,64 +1,58 @@ import React from 'react'; -import { observer, inject} from 'mobx-react'; +import {observer, inject} from 'mobx-react'; import styles from './Index.sass'; -import { routeNode } from 'react-router5'; @inject("tabStore") @observer class Index extends React.Component { + constructor(props) { + super(props); + } + _setActiveTab() { - const router = this.props.router; - const route = this.props.route; const tabStore = this.props.tabStore; - const { id } = route.params; + const {id} = this.props.route.params; tabStore.setActiveTab(id); } - constructor(props) { - super(props); - } - - // Triggered when a component will be scheduled to re-render because data it observes (from mobx store) has changed. + // Triggered when a component will be scheduled to re-render because data it observes has changed. // This makes it easy to trace renders back to the action that caused the rendering. componentWillReact() { - console.log("I will re-render, since the todo has changed!"); + console.debug("I will re-render, since the todo has changed!"); } - - // componentDidMount() is invoked immediately after a component is mounted. Initialization that requires DOM nodes should go here. I + // componentDidMount() is invoked immediately after a component is mounted. + // Initialization that requires DOM nodes should go here. // If you need to load data from a remote endpoint, this is a good place to instantiate the network request. // Setting state in this method will trigger a re-rendering. componentDidMount() { - console.log("I did mount Index"); this._setActiveTab(); } - componentWillUnmount(){ - console.log("I will unmount Index"); + componentWillUnmount() { } // This method is not called for the initial render. componentDidUpdate(prevProps, prevState) { this._setActiveTab(); - console.log("I will re-render for whatever reason"); + console.debug("I will re-render the Index Component for whatever reason"); } render() { const tabStore = this.props.tabStore; return ( -
-

Index, I am The Index Page {tabStore.activeTab}

-

This is some computed values:

- +
+

I am The Index Page

+
+

tabStore.activeTab: {tabStore.activeTab}

+

Tabs Number (computed tabStore.tabNumbers): {tabStore.tabNumbers}

+
+

(tabStore.tabs):

{JSON.stringify(tabStore.tabs)}
); } } -export default routeNode('index')(Index); +export default Index; diff --git a/src/components/Index/Index.sass b/src/components/Index/Index.sass new file mode 100644 index 0000000..e69de29 diff --git a/src/components/Index/index.js b/src/components/Index/index.js new file mode 100644 index 0000000..e997773 --- /dev/null +++ b/src/components/Index/index.js @@ -0,0 +1 @@ +export { default as Index} from "./Index.jsx"; diff --git a/src/components/Layout/Footer/Footer.jsx b/src/components/Layout/Footer/Footer.jsx index 09b1e8c..70c6209 100644 --- a/src/components/Layout/Footer/Footer.jsx +++ b/src/components/Layout/Footer/Footer.jsx @@ -1,20 +1,47 @@ import React from 'react'; - +import {withRoute, Link} from "react-mobx-router5"; import styles from './Footer.sass'; -export default class Footer extends React.Component { - static propTypes = { - name: React.PropTypes.string, - }; +function AnotherElement(props) { + return ( +
+

This is another component also wrapped with `withRoute`.
+ It will receive an `active` prop and an `active` className when the route is `/section/subsection/home`.

+ Try to navigate to {props.routerStore.router.buildPath('section.subsection.home')} and this background will change

+
+ ); +} + +const AnotherComponentWithRoute = withRoute(AnotherElement); + +class Footer extends React.Component { constructor(props) { super(props); } render() { + const style = { + fontWeight: 'bold', + }; + return ( -
This is the footer
+
This is the footer. It's been wrapped with `withRoute` HOC so it is aware of routes change and it will re-render on any route Change.
+

I did not pass a `routeName` prop to the resulting component so an `active` className will never be applied.

+ Nonetheless it is aware of the current route: {this.props.route.name}

+ + + +
); } } + + + + +export default withRoute(Footer); diff --git a/src/components/Layout/Footer/Footer.sass b/src/components/Layout/Footer/Footer.sass index e69de29..7ab9e72 100644 --- a/src/components/Layout/Footer/Footer.sass +++ b/src/components/Layout/Footer/Footer.sass @@ -0,0 +1,10 @@ +.container + +container + margin-top: 25px + +.another-element + border-top: 1px dashed grey + margin-top: 25px + + &:global(.active) + background: $green diff --git a/src/components/Layout/Header/Header.jsx b/src/components/Layout/Header/Header.jsx index a86c9ad..d06d47f 100644 --- a/src/components/Layout/Header/Header.jsx +++ b/src/components/Layout/Header/Header.jsx @@ -1,35 +1,41 @@ import React from 'react'; -import NavAccount from './NavAccount/NavAccount'; -import { BaseLink, withRoute } from 'react-router5'; +import {inject} from 'mobx-react'; -import styles from './Header.sass'; -import logo_img from './img/home_logo.jpg' +import NavMenu from './NavMenu/NavMenu'; +import * as styles from './Header.sass'; +import {Link} from "react-mobx-router5"; + +@inject('routerStore') class Header extends React.Component { render() { - const { router } = this.props; - + const routerStore = this.props.routerStore; return (
+ Home +
+ +
+
+

^ This is a Link Component => It will apply automatically an active class on the `a` element

+
+
+

These are NavLink Components => The `active` class will be applied to the 'li' wrappers ^

+
+
-
- - Logo - -
- -
- -
- -
); } } -// will pass the router trough context -export default withRoute(Header); + +// will passe the router trough context +export default Header; + diff --git a/src/components/Layout/Header/Header.sass b/src/components/Layout/Header/Header.sass index a40b7ba..e9ce4f3 100644 --- a/src/components/Layout/Header/Header.sass +++ b/src/components/Layout/Header/Header.sass @@ -2,25 +2,36 @@ $headerHeight: 126px .headerContainer - position: relative // so we can use absolute inside it - z-index: $zindex_headerContainer - height: $headerHeight - // **** - background: $orange - border-bottom: 2px solid $redSB-dark - border-top: 2px solid $redSB-dark + position: relative // so we can use absolute inside it + z-index: $zindex_headerContainer + height: $headerHeight + // **** + background: $red + border-bottom: 2px solid $red-dark + border-top: 2px solid $red-dark -.header - +container - +clearfix - position: relative - height: 100% +.explanation-home + position: absolute + top: 60px + left: 10px -.navAccountContainer - position: absolute - top: 0 - right: 0 - z-index: $zindex_navAccount +.explanation-nav + position: absolute + top: 90px + right: 10px +.header + +container + +clearfix + position: relative + height: 100% +.navContainer + position: absolute + top: 0 + right: 0 + z-index: $zindex_navAccount +.home-link + +simple-nav-link + text-decoration: none diff --git a/src/components/Layout/Header/NavAccount/NavAccount.jsx b/src/components/Layout/Header/NavAccount/NavAccount.jsx deleted file mode 100644 index f24e716..0000000 --- a/src/components/Layout/Header/NavAccount/NavAccount.jsx +++ /dev/null @@ -1,70 +0,0 @@ -import React from 'react'; -import { observer, inject } from 'mobx-react'; -import classNames from 'classnames'; -import { withRoute } from 'react-router5'; - -import styles from './NavAccount.sass'; - -@inject("userStore") -@observer -class NavAccount extends React.Component { - - constructor(props, context){ - super(props, context); - // this should work - // this.router = context.router; - this._showLogin = this._showLogin.bind(this); - } - - componentWillUnmount() { - console.log("Hello, I am the NavAccount, I will unmount") - } - - // =========== - // = Helpers = - // =========== - _showLogin(e) { - e.preventDefault(); - e.stopPropagation(); - e.nativeEvent.stopImmediatePropagation(); - - this.props.router.navigate('login', (err, state)=> { - if (err) console.log(err); - else console.log("I navigate to: " + state.name); - }); - } - - _renderMenu() { - const userStore = this.props.userStore; - - const isLoggedIn = userStore.isLoggedIn; - const ulClass = classNames(styles.navAccount, 'horizontal'); - - if (isLoggedIn){ - return ( - - ); - } - else { - return ( - - ); - } - - } - - render() { - return (this._renderMenu()) - } - -} - -export default withRoute(NavAccount); diff --git a/src/components/Layout/Header/NavAccount/NavAccount.sass b/src/components/Layout/Header/NavAccount/NavAccount.sass deleted file mode 100644 index 99a7f8f..0000000 --- a/src/components/Layout/Header/NavAccount/NavAccount.sass +++ /dev/null @@ -1,42 +0,0 @@ - -.navAccount - > li - height: 46px - text-align: center - +light-on-dark - - > a - display: inline-block - color: #FFF - font-size: 15px - line-height: 44px - padding: 0 18px - - &.sign-in - background: $redSB-dark2 - - &.sign-up - background: $redSB-light - - &.builder - background: $redSB-light2 - - &.finder - background: $redSB - - &.my-account - //background: $redSB-dark url('/public/images/__menu_my_account.png') no-repeat 120px 0 - padding-right: 40px - position: relative - - &:hover - background-position: 120px -47px - - span - border-bottom: none - - .menu-hover - display: block - - a - padding-right: 0 diff --git a/src/components/Layout/Header/NavMenu/NavMenu.jsx b/src/components/Layout/Header/NavMenu/NavMenu.jsx new file mode 100644 index 0000000..077c904 --- /dev/null +++ b/src/components/Layout/Header/NavMenu/NavMenu.jsx @@ -0,0 +1,80 @@ +import React from "react"; +import { inject, observer } from "mobx-react"; +import { BaseLink, Link, NavLink } from "react-mobx-router5"; +import * as styles from "./NavMenu.sass"; +import {} from 'react-mobx-router5'; + +function LoggedInMenu(props) { + const links = [ + { routeName: 'index', + routeParams: {id: 1}, + linkName: 'index' + }, + { routeName: 'section.home', + routeParams: {}, + linkName: 'Section/Home' + }, + { routeName: 'section.subsection.home', + routeParams: {}, + linkName: 'Section/SubSection/home' + }, + { routeName: 'section.subsection.login', + routeParams: {}, + linkName: 'login/logout' + } + ]; + + const Navs = links.map((item, index) => { + return ( + + {item.linkName} + ); + }); + + return ( + + ); +} + + +function LoggedOutMenu(props) { + return ( + + ); +} + + +@inject('routerStore', 'userStore') +@observer +class NavMenu extends React.Component { + + constructor(props) { + super(props); + this._showLogin = this._showLogin.bind(this); + } + + _showLogin(e) { + e.preventDefault(); + e.stopPropagation(); + e.nativeEvent.stopImmediatePropagation(); + console.log("hello"); + } + + render() { + + const isLoggedIn = this.props.userStore.isLoggedIn; + return ( + isLoggedIn ? : ); + } +} + +export default NavMenu; diff --git a/src/components/Layout/Header/NavMenu/NavMenu.sass b/src/components/Layout/Header/NavMenu/NavMenu.sass new file mode 100644 index 0000000..d8caef5 --- /dev/null +++ b/src/components/Layout/Header/NavMenu/NavMenu.sass @@ -0,0 +1,18 @@ + +.nav + +clearfix + +horizontal-list + > li + background: $red-dark2 + margin-left: 2px + //border-left: $red-light2 solid 1px + // on the the li, not on the `a` + &:global(.active) + background: $green + + > a + +nav-link + text-decoration: none + + + diff --git a/src/components/Layout/Header/img/home_logo.jpg b/src/components/Layout/Header/img/home_logo.jpg deleted file mode 100644 index 175ef0dee358adf02a206d16b65c035fe7ca6f21..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13732 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 -
-
-
-
+
+ +
-
+
-
+
-
); diff --git a/src/components/Layout/Layout.sass b/src/components/Layout/Layout.sass index 5c68a03..efc6c65 100644 --- a/src/components/Layout/Layout.sass +++ b/src/components/Layout/Layout.sass @@ -1,9 +1,20 @@ .body-loader - display: none + display: none body - background : $grey94 - font-size: 12px - line-height: 20px - width: 100% + background: $grey94 + font-size: 12px + line-height: 20px + width: 100% + +.main-container + +container + +clearfix + margin-top: 25px + + +.footer-container + border-top: 1px dashed grey + margin-top: 25px + diff --git a/src/components/Layout/Sidebar/Sidebar.jsx b/src/components/Layout/Sidebar/Sidebar.jsx deleted file mode 100644 index 97bc428..0000000 --- a/src/components/Layout/Sidebar/Sidebar.jsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; - -export default class Sidebar extends React.Component { - - constructor(props) { - super(props); - } - - render() { - return ( -
Sidebar
- ); - } -} diff --git a/src/components/Login/Login.jsx b/src/components/Login/Login.jsx new file mode 100644 index 0000000..f2af971 --- /dev/null +++ b/src/components/Login/Login.jsx @@ -0,0 +1,59 @@ +import React from 'react'; +import {fakeLogin, fakeLogout} from '../../actions/auth' +import {observer, inject} from 'mobx-react'; +import styles from './Login.sass' + +// 'inject' can be used to pick up the stores passed to components with Provider. +// It is a higher order component that takes a list of strings and makes those stores available as 'this.props.storeName' to the wrapped component. +@inject('userStore') +@observer +class Login extends React.Component { + + // To validate the injected store + static propTypes = { + userStore: React.PropTypes.object.isRequired + }; + + constructor(props) { + super(props); + // BINDING + this.onSubmit = this.onSubmit.bind(this); + this.onLogout = this.onLogout.bind(this); + } + + // ================== + // = Event Handlers = + // ================== + + onSubmit(event) { + event.preventDefault(); + fakeLogin(); + } + + onLogout(e) { + e.preventDefault(); + fakeLogout(); + } + + render() { + return ( +
+

{JSON.stringify(this.props.userStore.user)}

+
+

Click on login/logout and nav menu will change

+ + +
+ ); + } +} + +export default Login; diff --git a/src/components/Login/Login.sass b/src/components/Login/Login.sass new file mode 100644 index 0000000..c2f777c --- /dev/null +++ b/src/components/Login/Login.sass @@ -0,0 +1,3 @@ + +.container + +container diff --git a/src/components/Login/index.js b/src/components/Login/index.js new file mode 100644 index 0000000..418099c --- /dev/null +++ b/src/components/Login/index.js @@ -0,0 +1 @@ +export { default as Login} from "./Login"; diff --git a/src/components/Main/Main.jsx b/src/components/Main/Main.jsx index 033e242..cf64020 100644 --- a/src/components/Main/Main.jsx +++ b/src/components/Main/Main.jsx @@ -1,31 +1,24 @@ -import React, { createElement } from 'react'; -import { routeNode } from 'react-router5'; +import React from "react"; +import PropTypes from 'prop-types'; +import {routeNode, RouteView} from "react-mobx-router5"; +import routes from "../../routes"; -import Home from '../../pages/Home'; -import Index from '../../pages/Index'; -import Login from '../../pages/Login'; +const routeNodeName = ''; +class Main extends React.Component { + render() { + const {activeRoute} = this.props; + return ; + } +} -const components = { - 'home': Home, - 'index': Index, - 'login': Login, +// Both injected by routeNode +Main.propTypes = { + activeRoute: PropTypes.object.isRequired, // non-observable. plain js obj + routerStore: PropTypes.object.isRequired }; -class Main extends React.Component { - render(){ - const { route } = this.props; - const segment = route.name.split('.')[0]; - - return createElement(components[segment], - { ...route.params } - // For full unmount/mount pass the key as below: - // { key: route.meta.id, ...route.params } - ); - } -} -// withRouter is a High Order Function -// It is a sort of decorator to augment the Component class with extra methods or properties coming from react-router -// For example we can now access "this.props.router" (we need listenersPlugin from router5) -export default routeNode('')(Main); // '' because it it the root node +// higher-order component to wrap a route node component. +// '' if root node +export default routeNode(routeNodeName)(Main); diff --git a/src/components/Sections/Sections.jsx b/src/components/Sections/Sections.jsx new file mode 100644 index 0000000..44c753d --- /dev/null +++ b/src/components/Sections/Sections.jsx @@ -0,0 +1,23 @@ +import React from "react"; +import PropTypes from 'prop-types'; +import {routeNode, RouteView} from "react-mobx-router5"; +import routes from "../../routes"; + +const routeNodeName = 'section'; + +class Sections extends React.Component { + render() { + const {activeRoute} = this.props; + return ; + } +} + + +// Both injected by routeNode +Sections.propTypes = { + activeRoute: PropTypes.object.isRequired, + routerStore: PropTypes.object.isRequired +}; + +export default routeNode(routeNodeName)(Sections); + diff --git a/src/components/SubSections/SubSections.jsx b/src/components/SubSections/SubSections.jsx new file mode 100644 index 0000000..2559400 --- /dev/null +++ b/src/components/SubSections/SubSections.jsx @@ -0,0 +1,24 @@ +import React from "react"; +import PropTypes from 'prop-types'; +import {routeNode, RouteView} from "react-mobx-router5"; +import routes from "../../routes"; + +const routeNodeName = 'section.subsection'; + +class SubSections extends React.Component { + render() { + const {activeRoute} = this.props; + return ; + } +} + +// Both injected by routeNode +SubSections.propTypes = { + activeRoute: PropTypes.object.isRequired, + routerStore: PropTypes.object.isRequired +}; + +export default routeNode(routeNodeName)(SubSections); + + + diff --git a/src/config.js b/src/config.js index 502303e..21296fc 100644 --- a/src/config.js +++ b/src/config.js @@ -6,6 +6,6 @@ axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; axios.defaults.xsrfCookieName = 'csrftoken'; axios.defaults.xsrfHeaderName = 'X-CSRFToken'; -module.exports = {} +module.exports = {}; diff --git a/src/create-router5.js b/src/create-router5.js index 913059a..c6c44a2 100644 --- a/src/create-router5.js +++ b/src/create-router5.js @@ -1,7 +1,9 @@ import createRouter from 'router5'; import loggerPlugin from 'router5/plugins/logger'; -import listenersPlugin from 'router5/plugins/listeners'; import browserPlugin from 'router5/plugins/browser'; +import {mobxPlugin} from 'mobx-router5'; + +import routerStore from './stores/RouterStore'; import routes from './routes'; @@ -10,20 +12,16 @@ const routerOptions = { strictQueryParams: true }; -function configureRouter(useListenersPlugin = false) { - const router = createRouter(routes, routerOptions) - // Plugins - .usePlugin(loggerPlugin) - .usePlugin(browserPlugin({ - useHash: true - })); - - if (useListenersPlugin) { - router.usePlugin(listenersPlugin()); - } +// I can import the default module with whatever name I want (ex. `createRouter`) +export default function configureRouter() { + const router = createRouter(routes, routerOptions) + // Plugins + .usePlugin(browserPlugin({useHash: true})) + .usePlugin(mobxPlugin(routerStore)) + .usePlugin(loggerPlugin); - return router; + return router; } -// I can import the default module with whatever name I want (createRouter) -export default configureRouter; + + diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx deleted file mode 100644 index 743c50f..0000000 --- a/src/pages/Home.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import styles from './Home.sass'; -import { routeNode } from 'react-router5'; - -class Home extends React.Component { - render() { - return ( -
-

Home, I am a subroute page

- -
- ); - } -} - - -export default routeNode('home')(Home); diff --git a/src/pages/Home.sass b/src/pages/Home.sass deleted file mode 100644 index 0306c59..0000000 --- a/src/pages/Home.sass +++ /dev/null @@ -1,10 +0,0 @@ -.redBg - background-color: red - border-radius: 5px - color: white - padding: 5px - - -.container-border - border: 1px dashed grey - diff --git a/src/pages/Index.sass b/src/pages/Index.sass deleted file mode 100644 index f0d0927..0000000 --- a/src/pages/Index.sass +++ /dev/null @@ -1,10 +0,0 @@ -.redBg - background-color: red - border-radius: 5px - color: white - padding: 5px - - -.container-border - border: 1px dashed grey - diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx deleted file mode 100644 index b96ab06..0000000 --- a/src/pages/Login.jsx +++ /dev/null @@ -1,95 +0,0 @@ -import React from 'react'; -import {login, logout} from '../actions/auth' -import { observer, inject } from 'mobx-react'; -import { routeNode } from 'react-router5'; - -// 'inject' can be used to pick up the stores passed to components with Provider. -// It is a HOC that takes a list of strings and makes those stores available as 'this.props.storeName' to the wrapped component. -@inject('userStore') -@observer -class Login extends React.Component { - - // To validate the injected store - static propTypes = { - userStore: React.PropTypes.object.isRequired - }; - - constructor(props){ - super(props); - this.formData = {email:"", password:""}; - // BINDING - this.onEmailChange = this.onEmailChange.bind(this); - this.onPasswordChange = this.onPasswordChange.bind(this); - this.onSubmit = this.onSubmit.bind(this); - this.onLogout = this.onLogout.bind(this); - } - - componentDidMount(){ - console.log("I did mount the login"); - } - - componentWillUnmount(){ - console.log("I will unmount the login"); - } - - onEmailChange(e) { - Object.assign(this.formData, {}, {email: e.target.value}); - console.log(this.formData); - } - - onPasswordChange(e) { - Object.assign(this.formData, {}, {password: e.target.value}); - console.log(this.formData); - } - - onSubmit(event) { - event.preventDefault(); - login(this.formData); - } - - onLogout(e) { - event.preventDefault(); - logout(); - } - - render() { - return ( -
-

{JSON.stringify(this.props.userStore.user)}

-
-

Login

-
-
-
-
-
- -
- -
- - -
- ); - } -} - -export default routeNode('login')(Login); diff --git a/src/routes.js b/src/routes.js index 7766483..d43a0f1 100644 --- a/src/routes.js +++ b/src/routes.js @@ -1,5 +1,24 @@ +import {Home} from './components/Home'; +import {Index} from './components/Index'; +import {Login} from './components/Login'; +import Sections from './components/Sections/Sections'; +import SubSections from './components/SubSections/SubSections'; + + export default [ - { name: 'home', path: '/' }, - { name: 'login', path: '/login'}, - { name: 'index', path: '/index/:id'}, + { name: 'home', path: '/', component: Home}, + { name: 'login', path: '/login', component: Login}, + { name: 'index', path: '/index/:id', component: Index}, + { name: 'section', path: '/section', component: Sections, children: [ + // Sections + { name: 'home', path: '/home', component: Home }, + { name: 'login', path: '/login', component: Login }, + { name: 'index', path: '/index/:id', component: Index }, + { name: 'subsection', path: '/subsection', component: SubSections, children: [ + // Subsections + { name: 'home', path: '/home', component: Home }, + { name: 'login', path: '/login', component: Login }, + { name: 'index', path: '/index/:id', component: Index } + ]} + ]} ]; diff --git a/src/stores/RouterStore.js b/src/stores/RouterStore.js new file mode 100644 index 0000000..c42da8d --- /dev/null +++ b/src/stores/RouterStore.js @@ -0,0 +1,7 @@ +import {RouterStore} from 'mobx-router5'; + +// I instantiate it here because we could add something to the class before invoking new + +const routerStore = new RouterStore(); +export default routerStore; + diff --git a/src/stores/TabStore.js b/src/stores/TabStore.js index f261963..438d1b2 100644 --- a/src/stores/TabStore.js +++ b/src/stores/TabStore.js @@ -44,5 +44,5 @@ class TabStore { } -const tabStore = window.__tabStore__ = new TabStore(); +const tabStore = new TabStore(); export default tabStore; diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js index b149612..6d18605 100644 --- a/src/stores/UserStore.js +++ b/src/stores/UserStore.js @@ -1,22 +1,12 @@ import { observable, action, computed } from 'mobx'; -// TODO: -function _handleErrors(response) { - if (response.data && response.data.success !== true) { - throw Error(response); - } - return response; -} - class UserStore { - constructor() { this.user = this.defaultUser; this.watchlist = []; } - defaultUser = { "level" : "V", "status" : "U" @@ -51,7 +41,4 @@ class UserStore { const userStore = new UserStore(); - export default userStore; - -export { UserStore }; diff --git a/src/stores/index.js b/src/stores/index.js index 43fe932..abe7ee7 100644 --- a/src/stores/index.js +++ b/src/stores/index.js @@ -1,8 +1,17 @@ import tabStore from './TabStore'; import userStore from './UserStore'; +import routerStore from './RouterStore'; -export { - tabStore, +const stores = { userStore, + tabStore, + routerStore }; +window.__STORES__ = stores; // For Debug + +export { + userStore, + tabStore, + routerStore +}; diff --git a/src/styles/abstracts/_mixins.sass b/src/styles/abstracts/_mixins.sass index c179c02..10ed0d4 100644 --- a/src/styles/abstracts/_mixins.sass +++ b/src/styles/abstracts/_mixins.sass @@ -1,6 +1,6 @@ =container - margin: auto - width: $width + margin: auto + width: $width =clearfix &:after @@ -8,6 +8,12 @@ display: table clear: both +=horizontal-list + +clearfix + li + float: left + + // ================== // = FONT-SMOOTHING = // ================== @@ -16,16 +22,43 @@ // This is needed for light text on dark bg `bug` on chrome: http://usabilitypost.com/2012/11/05/stop-fixing-font-smoothing/ =font-smoothing-antialiased($value: on) @if $value == on - -webkit-font-smoothing: antialiased // DO NOT USE THIS SITE-WIDE + -webkit-font-smoothing: antialiased + // DO NOT USE THIS SITE-WIDE -moz-osx-font-smoothing: grayscale @else - -webkit-font-smoothing: subpixel-antialiased // On most non-retina displays this will give the sharpest text (default) + -webkit-font-smoothing: subpixel-antialiased + // On most non-retina displays this will give the sharpest text (default) -moz-osx-font-smoothing: auto - // It removes the subpixel smoothing (default) for light text on dark bg =light-on-dark +font-smoothing-antialiased(on) =dark-on-light +font-smoothing-antialiased(off) + + +=nav-link + display: inline-block + color: #FFF + font-size: 15px + line-height: 46px + padding: 0 18px + text-align: center + +light-on-dark + + + + &:hover + background: $orange + + &:global(.active) + background: $green + + +=simple-nav-link + +clearfix + float: left + //background: $red-dark2 + +nav-link + background: $red-dark2 diff --git a/src/styles/abstracts/_variables.sass b/src/styles/abstracts/_variables.sass index 918bb0e..001a5d7 100644 --- a/src/styles/abstracts/_variables.sass +++ b/src/styles/abstracts/_variables.sass @@ -1,27 +1,29 @@ - -$width : 960px +$width: 960px // Colors -$redSB : #87000D -$redSB-light : #A90010 -$redSB-light2 : #98000F -$redSB-dark : #640009 -$redSB-dark2: #75000A -$grey94 : #F1F1F1 +$red: #87000D +$red-light: #A90010 +$red-light2: #98000F +$red-dark: #640009 +$red-dark2: #75000A +$grey94: #F1F1F1 $orange: rgb(200, 42, 30) +$green: #3A9B28 +$cornflowerblue: #6495ed + // Fonts -$mainFont : "myriad-pro" -$fallBackFonts : "Myriad Pro", MyriadPro, Arial, sans-serif +$mainFont: Arial +$fallBackFonts: sans-serif // =========== // = Z-Index = // =========== -$zindex_headerContainer: 11 +$zindex_headerContainer: 11 -$zindex_navAccount: 14 -$zindex_navAccount__Dropdown: 13 -$zindex_socialbuttons: 12 +$zindex_navAccount: 14 +$zindex_navAccount__Dropdown: 13 +$zindex_socialbuttons: 12 -$zindex_menuNavigation: 13 +$zindex_menuNavigation: 13 $zindex_menuNavigationTooltips: 10 $zindex_menuNavigationDropdown: 10 diff --git a/src/styles/base/_commons.sass b/src/styles/base/_commons.sass index 0898aaa..82cb213 100644 --- a/src/styles/base/_commons.sass +++ b/src/styles/base/_commons.sass @@ -1,13 +1,3 @@ -// Display the list form left to right - -ul - horizontal - +clearfix - li - float: left - - -.clearfix - +clearfix - - +a + color: $cornflowerblue + text-decoration: underline diff --git a/webpack.config.js b/webpack.config.js index 2782ed5..1c307a9 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,64 +1,48 @@ "use strict"; -var webpack = require('webpack'); -var path = require('path'); -var loadersConf = require('./webpack.loaders'); -var DashboardPlugin = require('webpack-dashboard/plugin'); +const webpack = require('webpack'); +const path = require('path'); +const loadersConf = require('./webpack.loaders'); +const DashboardPlugin = require('webpack-dashboard/plugin'); const HOST = process.env.HOST || "127.0.0.1"; const PORT = process.env.PORT || "8888"; module.exports = { - // If you pass an array: All modules are loaded upon startup. The last one is exported. + // If you pass an array: All modules are loaded upon startup. + // The last one is exported. entry: [ - 'babel-polyfill', // emulate a full ES2015 (Promise, WeakMap, ectr) - 'react-hot-loader/patch', // needed for hot loader v3 - './src/app.jsx' // your app's entry point (exported module) + './src/app.jsx' // app's entry point (exported module) ], output: { - path: path.join(__dirname, 'public'), filename: 'bundle.js', + path: path.join(__dirname, './'), publicPath: 'http://localhost:8888/public/', }, module: { loaders: loadersConf }, resolve: { - extensions: ['', '.js', '.jsx'], + extensions: ['.js', '.jsx'], + modules: [ + path.join(__dirname, "src"), + path.join(__dirname, "node_modules"), // this was the 'fallback' option (For npm link-ed packages) + ], }, - devtool: '#inline-source-map', // 'eval-cheap-source-map', + devtool: '#inline-source-map', // or '#source-map' or 'eval-cheap-source-map' devServer: { - contentBase: "./", + contentBase: path.join(__dirname, "./"), publicPath: "http://localhost:8888/public/", - // do not print bundle build stats - noInfo: true, - // enable HMR - hot: true, - // embed the webpack-dev-server runtime into the bundle - inline: true, - // serve index.html in place of 404 responses to allow HTML5 history - historyApiFallback: true, + noInfo: true, // do not print bundle build stats + hot: true, // enable HMR + inline: true, // embed the webpack-dev-server runtime into the bundle + historyApiFallback: true, // serve index.html in place of 404 responses to allow HTML5 history port: PORT, - host: HOST, - colors: true, - // proxy to my local dev server running at 8000 port for anything starting with 'api' (then stripped) - proxy: { - '/api': { - target: 'http://localhost:8000', - pathRewrite: {'^/api' : ''}, - secure: false, - changeOrigin: true, - logLevel: 'debug', - } - } + host: HOST }, - // These files will imported in every sass file (imported) - sassResources: [ - './src/styles/abstracts/_variables.sass', - './src/styles/abstracts/_mixins.sass', - ], plugins: [ - new webpack.NoErrorsPlugin(), - new webpack.HotModuleReplacementPlugin(), - new DashboardPlugin(), // cool console output + new webpack.NoEmitOnErrorsPlugin(), + new webpack.HotModuleReplacementPlugin(), // enable HMR globally + new webpack.NamedModulesPlugin(), // prints more readable module names in the browser console on HMR updates + new DashboardPlugin(), // cool console output ] }; diff --git a/webpack.loaders.js b/webpack.loaders.js index 61fd9d8..bcfcb03 100644 --- a/webpack.loaders.js +++ b/webpack.loaders.js @@ -1,73 +1,141 @@ -var path = require('path'); +const path = require('path'); +const fs = require('fs'); -// Modularized sass files -const sassLoaders = [ - 'css-loader?sourceMap&camelCase=dashes&modules&importLoaders=1&localIdentName=[path]___[name]__[local]___[hash:base64:5]', - 'postcss-loader?sourceMap=inline', - 'sass-loader?sourceMap&outputStyle=expanded&indentedSyntax=sass&includePaths[]=' + path.resolve(__dirname, './src/styles'), - 'sass-resources' // NB: the config for sass-resources is in the the webpack config file +// Options passed to node-sass +const sassIncludePaths = [ + path.resolve(__dirname, 'src/styles'), ]; -// Not modularize sass files (globals) -const sassLoadersGloabals = [ - 'css-loader?sourceMap&camelCase=dashes&importLoaders=1', - 'postcss-loader?sourceMap=inline', - 'sass-loader?sourceMap&outputStyle=expanded&indentedSyntax=sass&includePaths[]=' + path.resolve(__dirname, './src/styles'), - 'sass-resources' // NB: the config for sass-resources is in the the webpack config file +// These files will be imported in every sass file +const sassResourcesPaths = [ + path.resolve(__dirname, 'src/styles/abstracts/_variables.sass'), + path.resolve(__dirname, 'src/styles/abstracts/_mixins.sass'), ]; module.exports = [ + // ============== + // = SourceMaps = + // ============== + // Get source maps from external node packages + { + test: /\.js?$/, + include: [ + // path.resolve doesn't work: https://github.com/webpack-contrib/source-map-loader/issues/40 + fs.realpathSync('./node_modules/mobx-router5'), + fs.realpathSync('./node_modules/react-mobx-router5') + ], + // exclude: /react-hot-loader/, + loader: "source-map-loader", + enforce: "pre" // This means this is a Prealoader (comes before) + }, + // ========= // = Babel = // ========= - // Load these exts with babel (so we can use 'import' instead of 'require' and es6 syntax) + // Load jsx extensions with babel (so we can use 'import' instead of 'require' and es6 syntax) { test: /\.jsx?$/, - include: __dirname + '/src/', - loader: "babel" + include: path.resolve(__dirname, 'src'), + use: ["babel-loader"] }, + + // ========= + // = Fonts = + // ========= // ========= // = Fonts = // ========= { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, - exclude: /(node_modules)/, - loader: "file" + exclude: path.resolve(__dirname, "node_modules"), + use: ["file-loader"] }, { test: /\.(woff|woff2)$/, - exclude: /(node_modules)/, - loader: "url?prefix=font/&limit=5000" + exclude: path.resolve(__dirname, "node_modules"), + use: [ + { + loader: "url-loader", + options: { + prefix: "font", + limit: 5000 + } + } + ] }, { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, - exclude: /(node_modules)/, - loader: "url?limit=10000&mimetype=application/octet-stream" + exclude: path.resolve(__dirname, "node_modules"), + use: [ + { + loader: "url-loader", + options: { + prefix: "font", + limit: 10000, + mimetype: "application/octet-stream" + } + } + ] }, + // ========== // = Images = // ========== { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, - exclude: /(node_modules)/, - loader: "url?limit=10000&mimetype=image/svg+xml" + exclude: path.resolve(__dirname, "node_modules"), + use: [ + { + loader: "url-loader", + options: { + limit: 10000, + mimetype: "image/svg+xml" + } + } + ] }, { test: /\.gif/, - exclude: /(node_modules)/, - loader: "url-loader?limit=10000&mimetype=image/gif" + exclude: path.resolve(__dirname, "node_modules"), + use: [ + { + loader: "url-loader", + options: { + limit: 10000, + mimetype: "image/gif" + } + } + ] }, { test: /\.jpg/, - exclude: /(node_modules)/, - loader: "url-loader?limit=10000&mimetype=image/jpg" + exclude: path.resolve(__dirname, "node_modules"), + use: [ + { + loader: "url-loader", + options: { + limit: 10000, + mimetype: "image/jpg" + } + } + ] }, { test: /\.png/, - exclude: /(node_modules)/, - loader: "url-loader?limit=10000&mimetype=image/png&name=[path][name].[ext]" + exclude: path.resolve(__dirname, "node_modules"), + use: [ + { + loader: "url-loader", + options: { + limit: 10000, + mimetype: "image/png", + name: "[path][name].[ext]" + } + } + ] }, + // ========== // = Styles = // ========== @@ -76,22 +144,107 @@ module.exports = [ // - style-loader will embed those styles directly into the markup(not when using dev-server) // Global CSS (from node_modules) + // ============================== { - test: /\.css$/, - include: __dirname + '/node_modules', - loader: 'style-loader!css-loader' + test: /\.css/, + include: path.resolve(__dirname, "node_modules"), + // Loaders can be chained by passing multiple loaders, which will be applied from right to left (last to first configured): + // - css-loader takes a CSS file and reads off all its dependencies + // - style-loader will embed those styles directly into the markup (not when using dev-server) + // TODO: for prod ==> loader: ExtractTextPlugin.extract('style-loader', 'css-loader') + use: [ + { + loader: "style-loader", + }, + { + loader: 'css-loader', + } + ] }, - // Global ('locals') sass imports. Do not modularize these imports (leave them as global css styles) + + // Global ('locals') sass imports. + // =============================== + // Do not modularize these imports (leave them as global css styles) { test: /\.(sass|scss)$/, - include: __dirname + '/src/styles/base', - loader: 'style-loader!' + sassLoadersGloabals.join('!') + include: path.resolve(__dirname, 'src/styles/base'), + use: [ + { + loader: "style-loader", + }, + { + loader: "css-loader", + options: { + sourceMap: true, + camelCase: "dashes", + importLoaders: 1 + } + }, + { + loader: "postcss-loader", + options: { + sourceMap: "inline", + } + }, + { + loader: "sass-loader", + options: { + sourceMap: true, + outputStyle: "expanded", + indentedSyntax: "sass", + includePaths: sassIncludePaths + } + }, + { + loader: "sass-resources-loader", + options: { + resources: sassResourcesPaths + }, + } + ] }, + // Local SASS modules + // =================== { test: /\.(sass|scss)$/, - exclude: __dirname + '/src/styles/base', - loader: 'style-loader!' + sassLoaders.join('!') - }, + exclude: path.resolve(__dirname, 'src/styles/base'), + use: [ + { + loader: "style-loader", + }, + { + loader: "css-loader", + options: { + sourceMap: true, + camelCase: "dashes", + importLoaders: 1, + modules: true, + localIdentName: "[path]___[name]__[local]___[hash:base64:5]" + } + }, + { + loader: "postcss-loader", + options: { + sourceMap: "inline", + } + }, + { + loader: "sass-loader", + options: { + sourceMap: true, + outputStyle: "expanded", + indentedSyntax: "sass", + includePaths: sassIncludePaths + } + }, + { + loader: "sass-resources-loader", + options: { + resources: sassResourcesPaths + }, + } + ] + } ]; diff --git a/webpack.production.config.js b/webpack.production.config.js index dbfe111..fbb7106 100644 --- a/webpack.production.config.js +++ b/webpack.production.config.js @@ -1,5 +1,5 @@ -// TODO +// TODO: not working for prod! var webpack = require('webpack'); var path = require('path');