mocha
and jsdom
](recipes/testing-with-mocha-and-jsdom.md)
+ * [Throwing errors with FB's invariant
library](recipes/throwing-errors-with-fbjs-invariant.md)
+
+See also [www.kriasoft.com/babel-starter-kit](https://www.kriasoft.com/babel-starter-kit/)
diff --git a/docs/assets/favicon.ico b/docs/assets/favicon.ico
new file mode 100644
index 0000000..977414b
Binary files /dev/null and b/docs/assets/favicon.ico differ
diff --git a/docs/assets/footer.css b/docs/assets/footer.css
new file mode 100644
index 0000000..af21c36
--- /dev/null
+++ b/docs/assets/footer.css
@@ -0,0 +1,12 @@
+/**
+ * Babel Starter Kit (https://www.kriasoft.com/babel-starter-kit)
+ *
+ * Copyright © 2015-2016 Kriasoft, LLC. All rights reserved.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
+ */
+
+.footer {
+
+}
diff --git a/docs/assets/footer.ejs b/docs/assets/footer.ejs
new file mode 100644
index 0000000..e69de29
diff --git a/docs/assets/header.css b/docs/assets/header.css
new file mode 100644
index 0000000..edfa55b
--- /dev/null
+++ b/docs/assets/header.css
@@ -0,0 +1,124 @@
+/**
+ * Babel Starter Kit (https://www.kriasoft.com/babel-starter-kit)
+ *
+ * Copyright © 2015-2016 Kriasoft, LLC. All rights reserved.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
+ */
+
+.header {
+ padding: 1.5rem 0;
+ background: var(--header-bg-color);
+ color: var(--header-color);
+}
+
+.header__logo {
+ color: var(--logo-color);
+ text-decoration: none;
+ text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);
+ font-weight: 700;
+ font-style: italic;
+ font-size: 2.5rem;
+ font-family: 'Lato', sans-serif;
+}
+
+.header__logo-img {
+ margin-right: 10px;
+ width: 164px;
+ height: 64px;
+ vertical-align: middle;
+}
+
+.header__author {
+ float: right;
+ padding: 18px 0;
+ font-size: 18px;
+ color: rgba(255, 255, 255, .6);
+}
+.header__author a {
+ color: rgba(255, 255, 255, .8);
+ text-decoration: none;
+}
+.header__author a:hover {
+ color: rgba(255, 255, 255, 1);
+}
+
+.header__hero {
+ padding: 50px 0 30px;
+}
+
+.header__hero-title {
+ margin: 0;
+ padding: 0 0 15px;
+ font-weight: 400;
+ font-size: 2rem;
+ line-height: 1em;
+}
+
+.header__hero-desc {
+ margin: 0;
+ padding: 0 0 30px;
+ letter-spacing: 1px;
+ font-weight: 300;
+ font-size: 1.5rem;
+}
+
+.header__hero-img {
+ margin: -20px -20px 0 0;
+ float: right;
+ width: 256px;
+ height: 256px;
+}
+
+.header__hero-btn {
+ display: inline-block;
+ margin: 1rem 1rem 0 0;
+ padding: .8rem 2rem .875rem 2rem;
+ border-radius: 3px;
+ background: rgba(249, 220, 62, .9);
+ color: var(--header-bg-color);
+ text-decoration: none;
+ box-shadow: 1px 1px 1px rgba(0, 0, 0, 1);
+}
+
+.header__hero-btn:hover {
+ background: rgba(249, 220, 62, 1);
+}
+
+.header__hero-btn--muted {
+ background: rgba(255, 255, 255, .5);
+}
+
+.header__hero-btn--muted:hover {
+ background: rgba(255, 255, 255, .6);
+}
+
+.header__hero-btn-icon {
+ width: 28px;
+ height: 28px;
+ vertical-align: middle;
+ margin: -4px 10px -4px 0;
+ fill: var(--header-bg-color);
+}
+
+.header__version {
+ background: color(var(--header-bg-color) alpha(-90%));
+ padding: 15px 0;
+ font-size: 16px;
+ color: var(--main-color);
+}
+
+.header__version a {
+ color: var(--link-color);
+ text-decoration: none;
+}
+
+.header__version a:hover,
+.header__version a:active {
+ color: color(var(--link-color) blackness(10%));
+}
+
+.header__join {
+ float: right;
+}
diff --git a/docs/assets/header.ejs b/docs/assets/header.ejs
new file mode 100644
index 0000000..b911023
--- /dev/null
+++ b/docs/assets/header.ejs
@@ -0,0 +1,31 @@
+Babel Starter Kit is a project template for
authoring and publishing JavaScript libraries
+ + + <%= github.subscribers_count %> watchers + + + + <%= github.watchers %> stars + + + + <%= github.forks %> forks + + + + <%= github.open_issues_count %> open issues + +
diff --git a/docs/assets/vendor/highlight.css b/docs/assets/vendor/highlight.css new file mode 100644 index 0000000..e69de29 diff --git a/docs/assets/vendor/highlight.js b/docs/assets/vendor/highlight.js new file mode 100644 index 0000000..e69de29 diff --git a/docs/assets/vendor/normalize.css b/docs/assets/vendor/normalize.css new file mode 100644 index 0000000..5e5e3c8 --- /dev/null +++ b/docs/assets/vendor/normalize.css @@ -0,0 +1,424 @@ +/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ + +/** + * 1. Set default font family to sans-serif. + * 2. Prevent iOS and IE text size adjust after device orientation change, + * without disabling user zoom. + */ + +html { + font-family: sans-serif; /* 1 */ + -ms-text-size-adjust: 100%; /* 2 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/** + * Remove default margin. + */ + +body { + margin: 0; +} + +/* HTML5 display definitions + ========================================================================== */ + +/** + * Correct `block` display not defined for any HTML5 element in IE 8/9. + * Correct `block` display not defined for `details` or `summary` in IE 10/11 + * and Firefox. + * Correct `block` display not defined for `main` in IE 11. + */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} + +/** + * 1. Correct `inline-block` display not defined in IE 8/9. + * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. + */ + +audio, +canvas, +progress, +video { + display: inline-block; /* 1 */ + vertical-align: baseline; /* 2 */ +} + +/** + * Prevent modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/** + * Address `[hidden]` styling not present in IE 8/9/10. + * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22. + */ + +[hidden], +template { + display: none; +} + +/* Links + ========================================================================== */ + +/** + * Remove the gray background color from active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * Improve readability of focused elements when they are also in an + * active/hover state. + */ + +a:active, +a:hover { + outline: 0; +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Address styling not present in IE 8/9/10/11, Safari, and Chrome. + */ + +abbr[title] { + border-bottom: 1px dotted; +} + +/** + * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. + */ + +b, +strong { + font-weight: bold; +} + +/** + * Address styling not present in Safari and Chrome. + */ + +dfn { + font-style: italic; +} + +/** + * Address variable `h1` font-size and margin within `section` and `article` + * contexts in Firefox 4+, Safari, and Chrome. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/** + * Address styling not present in IE 8/9. + */ + +mark { + background: #ff0; + color: #000; +} + +/** + * Address inconsistent and variable font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` affecting `line-height` in all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove border when inside `a` element in IE 8/9/10. + */ + +img { + border: 0; +} + +/** + * Correct overflow not hidden in IE 9/10/11. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* Grouping content + ========================================================================== */ + +/** + * Address margin not present in IE 8/9 and Safari. + */ + +figure { + margin: 1em 40px; +} + +/** + * Address differences between Firefox and other browsers. + */ + +hr { + box-sizing: content-box; + height: 0; +} + +/** + * Contain overflow in all browsers. + */ + +pre { + overflow: auto; +} + +/** + * Address odd `em`-unit font size rendering in all browsers. + */ + +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +/* Forms + ========================================================================== */ + +/** + * Known limitation: by default, Chrome and Safari on OS X allow very limited + * styling of `select`, unless a `border` property is set. + */ + +/** + * 1. Correct color not being inherited. + * Known issue: affects color of disabled elements. + * 2. Correct font properties not being inherited. + * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. + */ + +button, +input, +optgroup, +select, +textarea { + color: inherit; /* 1 */ + font: inherit; /* 2 */ + margin: 0; /* 3 */ +} + +/** + * Address `overflow` set to `hidden` in IE 8/9/10/11. + */ + +button { + overflow: visible; +} + +/** + * Address inconsistent `text-transform` inheritance for `button` and `select`. + * All other form control elements do not inherit `text-transform` values. + * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. + * Correct `select` style inheritance in Firefox. + */ + +button, +select { + text-transform: none; +} + +/** + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Correct inability to style clickable `input` types in iOS. + * 3. Improve usability and consistency of cursor style between image-type + * `input` and others. + */ + +button, +html input[type="button"], /* 1 */ +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; /* 2 */ + cursor: pointer; /* 3 */ +} + +/** + * Re-set default cursor for disabled elements. + */ + +button[disabled], +html input[disabled] { + cursor: default; +} + +/** + * Remove inner padding and border in Firefox 4+. + */ + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/** + * Address Firefox 4+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ + +input { + line-height: normal; +} + +/** + * It's recommended that you don't attempt to style these elements. + * Firefox's implementation doesn't respect box-sizing, padding, or width. + * + * 1. Address box sizing set to `content-box` in IE 8/9/10. + * 2. Remove excess padding in IE 8/9/10. + */ + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Fix the cursor style for Chrome's increment/decrement buttons. For certain + * `font-size` values of the `input`, it causes the cursor style of the + * decrement button to change from `default` to `text`. + */ + +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Address `appearance` set to `searchfield` in Safari and Chrome. + * 2. Address `box-sizing` set to `border-box` in Safari and Chrome. + */ + +input[type="search"] { + -webkit-appearance: textfield; /* 1 */ + box-sizing: content-box; /* 2 */ +} + +/** + * Remove inner padding and search cancel button in Safari and Chrome on OS X. + * Safari (but not Chrome) clips the cancel button when the search input has + * padding (and `textfield` appearance). + */ + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * Define consistent border, margin, and padding. + */ + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/** + * 1. Correct `color` not being inherited in IE 8/9/10/11. + * 2. Remove padding so people aren't caught out if they zero out fieldsets. + */ + +legend { + border: 0; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Remove default vertical scrollbar in IE 8/9/10/11. + */ + +textarea { + overflow: auto; +} + +/** + * Don't inherit the `font-weight` (applied by a rule above). + * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. + */ + +optgroup { + font-weight: bold; +} + +/* Tables + ========================================================================== */ + +/** + * Remove most spacing between table cells. + */ + +table { + border-collapse: collapse; + border-spacing: 0; +} + +td, +th { + padding: 0; +} diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..59a517d --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,57 @@ +--- +id: bsk:getting-started +title: Getting Started ∙ Babel Starter Kit +--- + +# Getting Started + +For better experience, make sure that you have `npm v3+` installed. Start by cloning this repo and +installing project dependencies: + +```sh +$ git clone -o babel-starter-kit \ + -b master --single-branch \ + https://github.com/kriasoft/babel-starter-kit.git \ +invariant
library
+
+If you're familiar with Facebook's [React](https://github.com/facebook/react),
+[React Native](https://github.com/facebook/react-native), [Flux](https://github.com/facebook/flux),
+[Relay](https://github.com/facebook/relay) libraries, you might notice the extensive use of the
+`invariant` function from `fbjs` npm module in their code bases. The goal of which is being able to
+provide descriptive error messages for the development environment. These error messages are going
+to be replaced by a single generic error when the project is compiled for production environments by
+Babel and a module bundler, such as Webpack or Browserify. This allows to minimize the client-side
+bundle size, and at the same time provide a good developer experience.
+
+Here is how it works. First, you need to install `fbjs` and `fbjs-scripts` npm modules by running:
+
+```sh
+$ npm install fbjs@next --save
+$ npm install fbjs-scripts@next --save-dev
+```
+
+Then update Babel configuration located in `pacakge.json` file by including `dev-expression` plugin
+from the `fbjs-scripts` npm module:
+
+```json
+{
+ "babel": {
+ "presets": [
+ "es2015",
+ "stage-0"
+ ],
+ "plugins": [
+ "fbjs-scripts/babel-6/dev-expression",
+ "transform-runtime"
+ ]
+ }
+}
+```
+
+Now you can use the `invariant` helper function in your code as follows:
+
+```js
+import invariant from modules
+
+class Greeting {
+
+ constructor(name) {
+ if (name) {
+ invariant(
+ typeof name === 'string' || name instanceof String,
+ 'The name argument must be a string'
+ );
+ this.name = name;
+ } else {
+ this.name = 'Guest';
+ }
+ this.name = name || 'Guest';
+ }
+
+ hello() {
+ return `Welcome, ${this.name}!`;
+ }
+
+}
+
+export default Greeting;
+```
+
+When you bundle your project for a production environment, you need to make sure that
+`process.env.NODE_ENV` variable is set to `'production'`. Here is an example how to do it with
+Webpack — [webpack.config.js#L28](https://github.com/kriasoft/react-starter-kit/blob/master/tools/webpack.config.js#L28)
+in [React Starter Kit](https://www.reactstarterkit.com).
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..f10fbea
--- /dev/null
+++ b/package.json
@@ -0,0 +1,82 @@
+{
+ "private": true,
+ "name": "mobx-router5-react",
+ "version": "0.0.0",
+ "description": "Router5 integration with mobX and react",
+ "homepage": "https://github.com/LeonardoGentile/mobx-router5-react",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/LeonardoGentile/mobx-router5-react.git"
+ },
+ "author": "Leonardo Gentile",
+ "contributors": [],
+ "license": "MIT",
+ "keywords": [
+ "router",
+ "mobx",
+ "react",
+ "router5",
+ "functional",
+ "routing"
+ ],
+ "bugs": {
+ "url": "https://github.com/LeonardoGentile/mobx-router5-react/issues"
+ },
+ "main": "index.js",
+ "jsnext:main": "index.es.js",
+ "babel": {
+ "presets": [
+ "latest",
+ "stage-0"
+ ],
+ "plugins": [
+ "transform-runtime",
+ "transform-decorators-legacy"
+ ]
+ },
+ "eslintConfig": {
+ "parser": "babel-eslint",
+ "extends": "airbnb-base"
+ },
+ "dependencies": {
+ "babel-runtime": "^6.11.6"
+ },
+ "devDependencies": {
+ "babel-cli": "^6.16.0",
+ "babel-core": "^6.17.0",
+ "babel-eslint": "^7.0.0",
+ "babel-plugin-transform-decorators-legacy": "^1.3.4",
+ "babel-plugin-transform-runtime": "^6.15.0",
+ "babel-preset-latest": "^6.16.0",
+ "babel-preset-stage-0": "^6.16.0",
+ "babel-register": "^6.16.3",
+ "chai": "^4.0.0-canary.1",
+ "coveralls": "^2.11.14",
+ "del": "^2.2.2",
+ "eslint": "^3.8.0",
+ "eslint-config-airbnb-base": "^10.0.1",
+ "eslint-plugin-import": "^2.2.0",
+ "istanbul": "^1.1.0-alpha.1",
+ "mocha": "^3.1.2",
+ "rollup": "^0.36.3",
+ "rollup-plugin-babel": "^2.6.1",
+ "sinon": "^2.0.0-pre.3"
+ },
+ "peerDependencies": {
+ "react": "^15.0.0",
+ "mobx": "^3.1.0",
+ "mobx-router5": "^0.0.0",
+ "mobx-react": "^4.0.0"
+ },
+ "scripts": {
+ "lint": "eslint src test tools",
+ "test": "mocha --compilers js:babel-register",
+ "test:watch": "mocha --compilers js:babel-register --reporter min --watch",
+ "test:cover": "babel-node ./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha",
+ "coveralls": "cat ./coverage/lcov.info | coveralls",
+ "build": "node tools/build",
+ "prepublish": "npm run build",
+ "publish:docs": "easystatic deploy docs --repo kriasoft/babel-starter-kit",
+ "start": "easystatic start docs"
+ }
+}
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000..7b857a0
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,7 @@
+import routeNode from './modules/routeNode';
+import { getComponentFromRoutes } from './modules/utils.js';
+
+export {
+ routeNode,
+ getComponentFromRoutes
+};
diff --git a/src/modules/BaseLink.js b/src/modules/BaseLink.js
new file mode 100644
index 0000000..b54f231
--- /dev/null
+++ b/src/modules/BaseLink.js
@@ -0,0 +1,94 @@
+import React, { Component } from 'react';
+import { observer, inject} from 'mobx-react';
+
+@inject('routerStore')
+class BaseLink extends Component {
+
+ constructor(props, context) {
+ super(props, context);
+
+ this.routerStore = props.routerStore;
+
+ // get the router instance from the props (when explicitly passed) or from routerStore
+ this.router = this.props.router || this.routerStore.router || null;
+
+ if (!this.router) {
+ console.error('[react-router5][BaseLink] missing router instance props');
+ }
+
+ if (!this.router.hasPlugin('BROWSER_PLUGIN')) {
+ console.error('[react-router5][BaseLink] missing browser plugin, href might be build incorrectly');
+ }
+
+ this.isActive = this.isActive.bind(this);
+ this.clickHandler = this.clickHandler.bind(this);
+
+ this.state = {
+ active: this.isActive(),
+ route: this.routerStore.route,
+ previousRoute: this.routerStore.previousRoute
+ };
+ }
+
+ buildUrl(routeName, routeParams) {
+ // If browser plugin is active
+ if (this.router.buildUrl) {
+ return this.router.buildUrl(routeName, routeParams);
+ }
+
+ return this.router.buildPath(routeName, routeParams);
+ }
+
+ isActive() {
+ return this.routerStore.isActive(this.props.routeName, this.props.routeParams, this.props.activeStrict);
+ }
+
+ clickHandler(evt) {
+ if (this.props.onClick) {
+ this.props.onClick(evt);
+
+ if (evt.defaultPrevented) {
+ return;
+ }
+ }
+
+ const comboKey = evt.metaKey || evt.altKey || evt.ctrlKey || evt.shiftKey;
+
+ if (evt.button === 0 && !comboKey) {
+ evt.preventDefault();
+ this.router.navigate(this.props.routeName, this.props.routeParams, this.props.routeOptions);
+ }
+ }
+
+ render() {
+ const {routeName, routeParams, className, activeClassName, children} = this.props;
+
+ const active = this.isActive();
+ const href = this.buildUrl(routeName, routeParams);
+ const linkclassName = (className ? className.split(' ') : [])
+ .concat(active ? [activeClassName] : []).join(' ');
+
+ const onClick = this.clickHandler;
+
+ return React.createElement('a', {href, className: linkclassName, onClick}, children);
+ }
+}
+
+
+// BaseLink.propTypes = {
+// routeName: React.PropTypes.string.isRequired,
+// routeParams: React.PropTypes.object,
+// routeOptions: React.PropTypes.object,
+// activeClassName: React.PropTypes.string,
+// activeStrict: React.PropTypes.bool,
+// onClick: React.PropTypes.func
+// };
+
+BaseLink.defaultProps = {
+ activeClassName: 'active',
+ activeStrict: false,
+ routeParams: {},
+ routeOptions: {}
+};
+
+export default BaseLink;
diff --git a/src/modules/routeNode.js b/src/modules/routeNode.js
new file mode 100644
index 0000000..040e314
--- /dev/null
+++ b/src/modules/routeNode.js
@@ -0,0 +1,56 @@
+import React, { Component, createElement } from 'react';
+import { getDisplayName } from './utils';
+import { autorun } from 'mobx';
+import { inject} from 'mobx-react';
+
+//TODO: create another wrapper function to pass a custom store name
+function routeNode(nodeName) { // route node Name
+ return function routeNodeWrapper(RouteSegment) { // component Name
+
+ @inject('routerStore')
+ class RouteNode extends Component {
+
+ constructor(props, context) {
+ super(props, context);
+ this.state = {
+ route: props.routerStore.route,
+ intersectionNode: props.routerStore.intersectionNode,
+ };
+ }
+
+ componentDidMount() {
+ this.autorunDisposer = autorun(() => {
+ this.setState({
+ route: this.props.routerStore.route,
+ intersectionNode: this.props.routerStore.intersectionNode
+ });
+ });
+ }
+
+ componentWillUnmount() {
+ this.autorunDisposer();
+ }
+ // Re-render the route-node (wrapped component) only if
+ // it is the correct "transition node"
+ shouldComponentUpdate (newProps, newState) {
+ return (newState.intersectionNode === nodeName);
+ }
+
+ render() {
+ const { props } = this;
+ const component = createElement(
+ RouteSegment,
+ { ...props }
+ );
+
+ return component;
+ }
+ }
+
+ RouteNode.displayName = 'RouteNode[' + getDisplayName(RouteSegment) + ']';
+
+ return RouteNode;
+ };
+}
+
+export default routeNode;
diff --git a/src/modules/utils.js b/src/modules/utils.js
new file mode 100644
index 0000000..91ebfb9
--- /dev/null
+++ b/src/modules/utils.js
@@ -0,0 +1,43 @@
+export const getDisplayName = component => component.displayName || component.name || 'Component';
+
+export const ifNot = (condition, errorMessage) => {
+ if (!condition) throw new Error(errorMessage);
+};
+
+/**
+ * Return a component to be rendered by a routeNode for the current route
+ * extracted from the custom (nested) routes definition (with 'component' field)
+ * @param {array} routes - nested routes def (with 'component' field)
+ * @param {object} routerStore - the mobx-router store. Used for getting the current route
+ * @param {string} routeNode - the name of the route for the React component from where to re-render (transition node)
+ * @returns {React.Component} - the component to be rendered
+ */
+export function getComponentFromRoutes(routes, routerStore, routeNode) {
+ let level = 0;
+ let currentLevel = 0;
+ // Break current route in segments (levels)
+ const routeSegments = routerStore.route.name.split('.');
+
+ // Set nesting level where to find routes for this routeNode
+ if (routeNode === '') level = 0;
+ else level = routeNode.split('.').length;
+ if (level === 0 && routeNode !== '') level = 1;
+
+ // Recurse until it gets the relevant portion of the routes obj or the component itself
+ function getComponent(routesObj) {
+ for(const route of routesObj) {
+ const segment = routeSegments[currentLevel]; // going deeper every recursion
+ if(route.name === segment) {
+
+ if(currentLevel >= level) { // Exit condition
+ return route.component;
+ }
+ else {
+ currentLevel += 1;
+ return getComponent(route.children);
+ }
+ }
+ }
+ }
+ return getComponent(routes);
+}
diff --git a/src/modules/withRoute.js b/src/modules/withRoute.js
new file mode 100644
index 0000000..3e917df
--- /dev/null
+++ b/src/modules/withRoute.js
@@ -0,0 +1,56 @@
+import React, { Component, createElement } from 'react';
+import { ifNot, getDisplayName } from './utils';
+
+function withRoute(BaseComponent) {
+ class ComponentWithRoute extends Component {
+ constructor(props, context) {
+ super(props, context);
+ this.router = context.router;
+ this.state = {
+ previousRoute: null,
+ route: this.router.getState()
+ };
+ this.listener = this.listener.bind(this);
+ }
+
+ componentDidMount() {
+ ifNot(
+ this.router.hasPlugin('LISTENERS_PLUGIN'),
+ '[react-router5][withRoute] missing listeners plugin'
+ );
+
+ this.listener = (toState, fromState) => this.setState({ previousRoute: fromState, route: toState });
+ this.router.addListener(this.listener);
+ }
+
+ componentWillUnmount() {
+ this.router.removeListener(this.listener);
+ }
+
+ listener(toState, fromState) {
+ this.setState({
+ previousRoute: fromState,
+ route: toState
+ });
+ }
+
+ render() {
+ ifNot(
+ !this.props.router && !this.props.route && !this.props.previousRoute,
+ '[react-router5] prop names `router`, `route` and `previousRoute` are reserved.'
+ );
+
+ return createElement(BaseComponent, { ...this.props, ...this.state, router: this.router });
+ }
+ }
+
+ ComponentWithRoute.contextTypes = {
+ router: React.PropTypes.object.isRequired
+ };
+
+ ComponentWithRoute.displayName = 'WithRoute[' + getDisplayName(BaseComponent) + ']';
+
+ return ComponentWithRoute;
+}
+
+export default withRoute;
diff --git a/test/.eslintrc b/test/.eslintrc
new file mode 100644
index 0000000..88e8b17
--- /dev/null
+++ b/test/.eslintrc
@@ -0,0 +1,16 @@
+{
+ "env": {
+ "mocha": true
+ },
+ "rules": {
+ "import/no-extraneous-dependencies": [
+ "error",
+ {
+ "optionalDependencies": false,
+ "peerDependencies": false
+ }
+ ],
+ "no-unused-expressions": "off",
+ "padded-blocks": "off"
+ }
+}
diff --git a/test/GreetingSpec.js b/test/GreetingSpec.js
new file mode 100644
index 0000000..f0a9272
--- /dev/null
+++ b/test/GreetingSpec.js
@@ -0,0 +1,31 @@
+/**
+ * Babel Starter Kit (https://www.kriasoft.com/babel-starter-kit)
+ *
+ * Copyright © 2015-2016 Kriasoft, LLC. All rights reserved.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
+ */
+
+import { expect } from 'chai';
+import Greeting from '../src/Greeting';
+
+describe('Greeting', () => {
+
+ describe('greeting.hello()', () => {
+
+ it('should return welcome message for a guest user', () => {
+ const greeting = new Greeting();
+ const message = greeting.hello();
+ expect(message).to.be.equal('Welcome, Guest!');
+ });
+
+ it('should return welcome message for a named user', () => {
+ const greeting = new Greeting('John');
+ const message = greeting.hello();
+ expect(message).to.be.equal('Welcome, John!');
+ });
+
+ });
+
+});
diff --git a/tools/.eslintrc b/tools/.eslintrc
new file mode 100644
index 0000000..6a0afaa
--- /dev/null
+++ b/tools/.eslintrc
@@ -0,0 +1,12 @@
+{
+ "rules": {
+ "import/no-extraneous-dependencies": [
+ "error",
+ {
+ "optionalDependencies": false,
+ "peerDependencies": false
+ }
+ ],
+ "strict": "off"
+ }
+}
diff --git a/tools/build.js b/tools/build.js
new file mode 100644
index 0000000..f647b8f
--- /dev/null
+++ b/tools/build.js
@@ -0,0 +1,53 @@
+/**
+ * Babel Starter Kit (https://www.kriasoft.com/babel-starter-kit)
+ *
+ * Copyright © 2015-2016 Kriasoft, LLC. All rights reserved.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
+ */
+
+'use strict';
+
+const fs = require('fs');
+const del = require('del');
+const rollup = require('rollup');
+const babel = require('rollup-plugin-babel');
+const pkg = require('../package.json');
+
+let promise = Promise.resolve();
+
+// Clean up the output directory
+promise = promise.then(() => del(['dist/*']));
+
+// Compile source code into a distributable format with Babel
+['es', 'cjs', 'umd'].forEach((format) => {
+ promise = promise.then(() => rollup.rollup({
+ entry: 'src/index.js',
+ external: Object.keys(pkg.dependencies),
+ plugins: [babel(Object.assign(pkg.babel, {
+ babelrc: false,
+ exclude: 'node_modules/**',
+ runtimeHelpers: true, // because we use transform-runtime plugin (avoid repetition)
+ presets: pkg.babel.presets.map(x => (x === 'latest' ? ['latest', { es2015: { modules: false } }] : x)),
+ }))],
+ }).then(bundle => bundle.write({
+ dest: `dist/${format === 'cjs' ? 'index' : `index.${format}`}.js`,
+ format,
+ sourceMap: true,
+ moduleName: format === 'umd' ? pkg.name : undefined,
+ })));
+});
+
+// Copy package.json and LICENSE.txt
+promise = promise.then(() => {
+ delete pkg.private;
+ delete pkg.devDependencies;
+ delete pkg.scripts;
+ delete pkg.eslintConfig;
+ delete pkg.babel;
+ fs.writeFileSync('dist/package.json', JSON.stringify(pkg, null, ' '), 'utf-8');
+ fs.writeFileSync('dist/LICENSE.txt', fs.readFileSync('LICENSE.txt', 'utf-8'), 'utf-8');
+});
+
+promise.catch(err => console.error(err.stack)); // eslint-disable-line no-console