diff --git a/README.md b/README.md
index 63581aa..c233725 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,22 @@
### Preact Routlet
-This package contains routing functionalities for [preact](https://github.com/developit/preact) applications. Instead of using HTML5 history API it uses the oldie `/#/what-ever` hash routing (this will change in the future).
+This package contains routing functionalities for [preact](https://github.com/developit/preact) and, now from 0.3.0 `React` applications as well. Instead of using HTML5 history API it uses the oldie `/#/what-ever` hash routing (this will change in the future).
This project was created by exploring contextual ways to define routes rather than placing all the routes in a single file.
##### Usage:
+Available imports:
+
+```javascript
+// if you're using React
+import { renderOnRoute, navigate, RouterOutlet, PathLookup, routePool, Link } from "preact-routlet/react"
+// if you're using Preact
+import { renderOnRoute, navigate, RouterOutlet, PathLookup, routePool, Link } from "preact-routlet/preact"
+```
+
+Either `from "preact-routlet/preact"` or `from "preact-routlet/react"`
+
Place your RouterOutlet element somewhere in your JSX:
```html
@@ -85,7 +96,6 @@ So basically you're registering all the components that listen to route changes
- Navigation (code)
```javascript
- import { navigate } from "preact-routlet";
...
navigate("/somewhere");
```
diff --git a/package.json b/package.json
index 8adc7d3..ed5fe1f 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,9 @@
"version": "0.2.1",
"description": "simple preact hash routing",
"scripts": {
- "build": "rollup -c rollup.config.js"
+ "build": "npm run build:react ; npm run build:preact",
+ "build:react": "rollup -c rollup.config.react.js",
+ "build:preact": "rollup -c rollup.config.preact.js"
},
"main": "routlet.js",
"keywords": [],
@@ -12,13 +14,17 @@
"devDependencies": {
"babel-core": "^6.26.0",
"babel-plugin-external-helpers": "^6.22.0",
+ "babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-preset-env": "^1.6.1",
"rollup": "^0.50.0",
"rollup-plugin-babel": "^3.0.2"
},
"dependencies": {
- "path-parser": "^2.0.2",
- "preact": "^8.2.6"
+ "path-parser": "^2.0.2"
+ },
+ "peerDependencies": {
+ "preact": "^8.2.6",
+ "react": "^16.0.0"
}
}
diff --git a/index.js b/preact.dev.js
similarity index 93%
rename from index.js
rename to preact.dev.js
index 169f51a..cc16632 100644
--- a/index.js
+++ b/preact.dev.js
@@ -28,13 +28,14 @@ export const navigate = newUrl => location.hash = "#" + newUrl;
export class PathLookup extends Component {
+ state = {
+ params: null,
+ path: location.hash ? transformHash(location.hash): "/",
+ current: null
+ }
+
componentWillMount() {
gotoDefault();
- this.setState({
- params: null,
- path: location.hash ? transformHash(location.hash): "/",
- current: null
- });
this.hashChange(this.state.path);
}
diff --git a/routlet.js b/preact.js
similarity index 84%
rename from routlet.js
rename to preact.js
index c887da8..1ae05d2 100644
--- a/routlet.js
+++ b/preact.js
@@ -1,7 +1,7 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('preact'), require('path-parser')) :
typeof define === 'function' && define.amd ? define(['exports', 'preact', 'path-parser'], factory) :
- (factory((global.routlet = {}),global.preact,global.Path));
+ (factory((global.routletPreact = {}),global.preact,global.Path));
}(this, (function (exports,preact,Path) { 'use strict';
Path = Path && Path.hasOwnProperty('default') ? Path['default'] : Path;
@@ -246,19 +246,27 @@ var PathLookup = function (_Component) {
inherits(PathLookup, _Component);
function PathLookup() {
+ var _ref;
+
+ var _temp, _this, _ret;
+
classCallCheck(this, PathLookup);
- return possibleConstructorReturn(this, (PathLookup.__proto__ || Object.getPrototypeOf(PathLookup)).apply(this, arguments));
+
+ for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
+ args[_key] = arguments[_key];
+ }
+
+ return _ret = (_temp = (_this = babelHelpers.possibleConstructorReturn(this, (_ref = PathLookup.__proto__ || Object.getPrototypeOf(PathLookup)).call.apply(_ref, [this].concat(args))), _this), _this.state = {
+ params: null,
+ path: location.hash ? transformHash(location.hash) : "/",
+ current: null
+ }, _temp), babelHelpers.possibleConstructorReturn(_this, _ret);
}
createClass(PathLookup, [{
key: "componentWillMount",
value: function componentWillMount() {
gotoDefault();
- this.setState({
- params: null,
- path: location.hash ? transformHash(location.hash) : "/",
- current: null
- });
this.hashChange(this.state.path);
}
}, {
@@ -266,8 +274,8 @@ var PathLookup = function (_Component) {
value: function componentDidMount() {
var _this2 = this;
- window.addEventListener("hashchange", function (_ref) {
- var newURL = _ref.newURL;
+ window.addEventListener("hashchange", function (_ref2) {
+ var newURL = _ref2.newURL;
return _this2.hashChange(transformHash(newURL || location.hash));
});
}
@@ -278,10 +286,10 @@ var PathLookup = function (_Component) {
}
}, {
key: "render",
- value: function render(_ref2, _ref3) {
- var shouldRender = _ref2.shouldRender,
- children = _ref2.children;
- var path = _ref3.path;
+ value: function render(_ref3, _ref4) {
+ var shouldRender = _ref3.shouldRender,
+ children = _ref3.children;
+ var path = _ref4.path;
return shouldRender(path) ? children[0] : null;
}
@@ -311,16 +319,16 @@ var RouterOutlet = function (_PathLookup) {
}
}, {
key: "render",
- value: function render(_ref4, _ref5) {
- var children = _ref4.children,
- _ref4$shouldRedirect = _ref4.shouldRedirect,
- shouldRedirect = _ref4$shouldRedirect === undefined ? function (_) {
+ value: function render(_ref5, _ref6) {
+ var children = _ref5.children,
+ _ref5$shouldRedirect = _ref5.shouldRedirect,
+ shouldRedirect = _ref5$shouldRedirect === undefined ? function (_) {
return false;
- } : _ref4$shouldRedirect,
- redirect = _ref4.redirect;
- var current = _ref5.current,
- params = _ref5.params,
- path = _ref5.path;
+ } : _ref5$shouldRedirect,
+ redirect = _ref5.redirect;
+ var current = _ref6.current,
+ params = _ref6.params,
+ path = _ref6.path;
var result = current ? preact.h(current, { params: params, path: path }) : children[0];
@@ -335,10 +343,10 @@ var RouterOutlet = function (_PathLookup) {
return RouterOutlet;
}(PathLookup);
-var Link = function Link(_ref6) {
- var href = _ref6.href,
- children = _ref6.children,
- props = objectWithoutProperties(_ref6, ["href", "children"]);
+var Link = function Link(_ref7) {
+ var href = _ref7.href,
+ children = _ref7.children,
+ props = objectWithoutProperties(_ref7, ["href", "children"]);
return preact.h("a", _extends({ href: "#" + href }, props), children);
};
diff --git a/react.dev.js b/react.dev.js
new file mode 100644
index 0000000..eb275e3
--- /dev/null
+++ b/react.dev.js
@@ -0,0 +1,80 @@
+import React from "react";
+import Path from 'path-parser';
+
+export const routePool = [];
+
+let FIRST_COMPONENT_HAS_MOUNTED = false;
+const gotoDefault = _ => {
+ if(!FIRST_COMPONENT_HAS_MOUNTED) {
+ if(!location.hash) setTimeout(navigate, 1, "/");
+ FIRST_COMPONENT_HAS_MOUNTED = true;
+ }
+}
+
+const transformHash = rawHash => rawHash.split("#").pop();
+
+export function renderOnRoute(path) {
+ return function(comp) {
+ routePool.push({
+ path,
+ parser: new Path(path),
+ comp,
+ });
+ return comp;
+ }
+}
+
+export const navigate = newUrl => location.hash = "#" + newUrl;
+
+export class PathLookup extends React.Component {
+
+ state = {
+ params: null,
+ path: location.hash ? transformHash(location.hash): "/",
+ current: null
+ }
+
+ componentWillMount() {
+ gotoDefault();
+ this.hashChange(this.state.path);
+ }
+
+ componentDidMount() {
+ window.addEventListener("hashchange", ({ newURL }) =>
+ this.hashChange(transformHash(newURL || location.hash)));
+ }
+
+ hashChange(selectedRoute) {
+ this.setState({ path: selectedRoute });
+ }
+
+ render() {
+ return this.props.shouldRender(this.state.path) ? this.props.children: null;
+ }
+}
+
+export class RouterOutlet extends PathLookup {
+
+ hashChange(selectedRoute) {
+ const selectedMatcher = routePool.find(matcher => !!matcher.parser.test(selectedRoute));
+ this.setState({
+ "params": selectedMatcher ? selectedMatcher.parser.test(selectedRoute): null,
+ "path": selectedRoute,
+ "current": selectedMatcher ? selectedMatcher.comp: null
+ });
+ }
+
+ render() {
+ const result = this.state.current ? React.createElement(this.state.current, { params: this.state.params, path: this.state.path }): this.props.children;
+
+ if(this.props.shouldRedirect && this.props.shouldRedirect(this.state.path) && this.state.current) {
+ navigate(this.props.redirect);
+ return null;
+ }
+
+ return result;
+ }
+}
+
+export const Link = ({ href, children, ...props }) =>
+ React.createElement("a", { href: `#${href}`, ...props }, children);
diff --git a/react.js b/react.js
new file mode 100644
index 0000000..b33f85a
--- /dev/null
+++ b/react.js
@@ -0,0 +1,349 @@
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react'), require('path-parser')) :
+ typeof define === 'function' && define.amd ? define(['exports', 'react', 'path-parser'], factory) :
+ (factory((global.routletReact = {}),global.React,global.Path));
+}(this, (function (exports,React,Path) { 'use strict';
+
+React = React && React.hasOwnProperty('default') ? React['default'] : React;
+Path = Path && Path.hasOwnProperty('default') ? Path['default'] : Path;
+
+var asyncGenerator = function () {
+ function AwaitValue(value) {
+ this.value = value;
+ }
+
+ function AsyncGenerator(gen) {
+ var front, back;
+
+ function send(key, arg) {
+ return new Promise(function (resolve, reject) {
+ var request = {
+ key: key,
+ arg: arg,
+ resolve: resolve,
+ reject: reject,
+ next: null
+ };
+
+ if (back) {
+ back = back.next = request;
+ } else {
+ front = back = request;
+ resume(key, arg);
+ }
+ });
+ }
+
+ function resume(key, arg) {
+ try {
+ var result = gen[key](arg);
+ var value = result.value;
+
+ if (value instanceof AwaitValue) {
+ Promise.resolve(value.value).then(function (arg) {
+ resume("next", arg);
+ }, function (arg) {
+ resume("throw", arg);
+ });
+ } else {
+ settle(result.done ? "return" : "normal", result.value);
+ }
+ } catch (err) {
+ settle("throw", err);
+ }
+ }
+
+ function settle(type, value) {
+ switch (type) {
+ case "return":
+ front.resolve({
+ value: value,
+ done: true
+ });
+ break;
+
+ case "throw":
+ front.reject(value);
+ break;
+
+ default:
+ front.resolve({
+ value: value,
+ done: false
+ });
+ break;
+ }
+
+ front = front.next;
+
+ if (front) {
+ resume(front.key, front.arg);
+ } else {
+ back = null;
+ }
+ }
+
+ this._invoke = send;
+
+ if (typeof gen.return !== "function") {
+ this.return = undefined;
+ }
+ }
+
+ if (typeof Symbol === "function" && Symbol.asyncIterator) {
+ AsyncGenerator.prototype[Symbol.asyncIterator] = function () {
+ return this;
+ };
+ }
+
+ AsyncGenerator.prototype.next = function (arg) {
+ return this._invoke("next", arg);
+ };
+
+ AsyncGenerator.prototype.throw = function (arg) {
+ return this._invoke("throw", arg);
+ };
+
+ AsyncGenerator.prototype.return = function (arg) {
+ return this._invoke("return", arg);
+ };
+
+ return {
+ wrap: function (fn) {
+ return function () {
+ return new AsyncGenerator(fn.apply(this, arguments));
+ };
+ },
+ await: function (value) {
+ return new AwaitValue(value);
+ }
+ };
+}();
+
+
+
+
+
+var classCallCheck = function (instance, Constructor) {
+ if (!(instance instanceof Constructor)) {
+ throw new TypeError("Cannot call a class as a function");
+ }
+};
+
+var createClass = function () {
+ function defineProperties(target, props) {
+ for (var i = 0; i < props.length; i++) {
+ var descriptor = props[i];
+ descriptor.enumerable = descriptor.enumerable || false;
+ descriptor.configurable = true;
+ if ("value" in descriptor) descriptor.writable = true;
+ Object.defineProperty(target, descriptor.key, descriptor);
+ }
+ }
+
+ return function (Constructor, protoProps, staticProps) {
+ if (protoProps) defineProperties(Constructor.prototype, protoProps);
+ if (staticProps) defineProperties(Constructor, staticProps);
+ return Constructor;
+ };
+}();
+
+
+
+
+
+
+
+var _extends = Object.assign || function (target) {
+ for (var i = 1; i < arguments.length; i++) {
+ var source = arguments[i];
+
+ for (var key in source) {
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
+ target[key] = source[key];
+ }
+ }
+ }
+
+ return target;
+};
+
+
+
+var inherits = function (subClass, superClass) {
+ if (typeof superClass !== "function" && superClass !== null) {
+ throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
+ }
+
+ subClass.prototype = Object.create(superClass && superClass.prototype, {
+ constructor: {
+ value: subClass,
+ enumerable: false,
+ writable: true,
+ configurable: true
+ }
+ });
+ if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
+};
+
+
+
+
+
+
+
+
+
+var objectWithoutProperties = function (obj, keys) {
+ var target = {};
+
+ for (var i in obj) {
+ if (keys.indexOf(i) >= 0) continue;
+ if (!Object.prototype.hasOwnProperty.call(obj, i)) continue;
+ target[i] = obj[i];
+ }
+
+ return target;
+};
+
+var possibleConstructorReturn = function (self, call) {
+ if (!self) {
+ throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
+ }
+
+ return call && (typeof call === "object" || typeof call === "function") ? call : self;
+};
+
+var routePool = [];
+
+var FIRST_COMPONENT_HAS_MOUNTED = false;
+var gotoDefault = function gotoDefault(_) {
+ if (!FIRST_COMPONENT_HAS_MOUNTED) {
+ if (!location.hash) setTimeout(navigate, 1, "/");
+ FIRST_COMPONENT_HAS_MOUNTED = true;
+ }
+};
+
+var transformHash = function transformHash(rawHash) {
+ return rawHash.split("#").pop();
+};
+
+function renderOnRoute(path) {
+ return function (comp) {
+ routePool.push({
+ path: path,
+ parser: new Path(path),
+ comp: comp
+ });
+ return comp;
+ };
+}
+
+var navigate = function navigate(newUrl) {
+ return location.hash = "#" + newUrl;
+};
+
+var PathLookup = function (_React$Component) {
+ inherits(PathLookup, _React$Component);
+
+ function PathLookup() {
+ var _ref;
+
+ var _temp, _this, _ret;
+
+ classCallCheck(this, PathLookup);
+
+ for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
+ args[_key] = arguments[_key];
+ }
+
+ return _ret = (_temp = (_this = babelHelpers.possibleConstructorReturn(this, (_ref = PathLookup.__proto__ || Object.getPrototypeOf(PathLookup)).call.apply(_ref, [this].concat(args))), _this), _this.state = {
+ params: null,
+ path: location.hash ? transformHash(location.hash) : "/",
+ current: null
+ }, _temp), babelHelpers.possibleConstructorReturn(_this, _ret);
+ }
+
+ createClass(PathLookup, [{
+ key: "componentWillMount",
+ value: function componentWillMount() {
+ gotoDefault();
+ this.hashChange(this.state.path);
+ }
+ }, {
+ key: "componentDidMount",
+ value: function componentDidMount() {
+ var _this2 = this;
+
+ window.addEventListener("hashchange", function (_ref2) {
+ var newURL = _ref2.newURL;
+ return _this2.hashChange(transformHash(newURL || location.hash));
+ });
+ }
+ }, {
+ key: "hashChange",
+ value: function hashChange(selectedRoute) {
+ this.setState({ path: selectedRoute });
+ }
+ }, {
+ key: "render",
+ value: function render() {
+ return this.props.shouldRender(this.state.path) ? this.props.children : null;
+ }
+ }]);
+ return PathLookup;
+}(React.Component);
+
+var RouterOutlet = function (_PathLookup) {
+ inherits(RouterOutlet, _PathLookup);
+
+ function RouterOutlet() {
+ classCallCheck(this, RouterOutlet);
+ return possibleConstructorReturn(this, (RouterOutlet.__proto__ || Object.getPrototypeOf(RouterOutlet)).apply(this, arguments));
+ }
+
+ createClass(RouterOutlet, [{
+ key: "hashChange",
+ value: function hashChange(selectedRoute) {
+ var selectedMatcher = routePool.find(function (matcher) {
+ return !!matcher.parser.test(selectedRoute);
+ });
+ this.setState({
+ "params": selectedMatcher ? selectedMatcher.parser.test(selectedRoute) : null,
+ "path": selectedRoute,
+ "current": selectedMatcher ? selectedMatcher.comp : null
+ });
+ }
+ }, {
+ key: "render",
+ value: function render() {
+ var result = this.state.current ? React.createElement(this.state.current, { params: this.state.params, path: this.state.path }) : this.props.children;
+
+ if (this.props.shouldRedirect && this.props.shouldRedirect(this.state.path) && this.state.current) {
+ navigate(this.props.redirect);
+ return null;
+ }
+
+ return result;
+ }
+ }]);
+ return RouterOutlet;
+}(PathLookup);
+
+var Link = function Link(_ref3) {
+ var href = _ref3.href,
+ children = _ref3.children,
+ props = objectWithoutProperties(_ref3, ["href", "children"]);
+ return React.createElement("a", _extends({ href: "#" + href }, props), children);
+};
+
+exports.routePool = routePool;
+exports.renderOnRoute = renderOnRoute;
+exports.navigate = navigate;
+exports.PathLookup = PathLookup;
+exports.RouterOutlet = RouterOutlet;
+exports.Link = Link;
+
+Object.defineProperty(exports, '__esModule', { value: true });
+
+})));
diff --git a/rollup.config.js b/rollup.common.js
similarity index 61%
rename from rollup.config.js
rename to rollup.common.js
index c9fffd6..13bff79 100644
--- a/rollup.config.js
+++ b/rollup.common.js
@@ -1,11 +1,6 @@
import babel from 'rollup-plugin-babel';
-export default {
- input: `index.js`,
- output: [
- { file: 'routlet.js', name: 'routlet', format: 'umd' },
- ],
- plugins: [
+export default [
babel({
exclude: 'node_modules/**',
"presets": [
@@ -18,8 +13,8 @@ export default {
],
"plugins": [
"external-helpers",
- "transform-object-rest-spread"
+ "transform-object-rest-spread",
+ "transform-class-properties"
]
})
]
-}
diff --git a/rollup.config.preact.js b/rollup.config.preact.js
new file mode 100644
index 0000000..bcfeac8
--- /dev/null
+++ b/rollup.config.preact.js
@@ -0,0 +1,9 @@
+import common from './rollup.common'
+
+export default {
+ input: `preact.dev.js`,
+ output: [
+ { file: 'preact.js', name: 'routletPreact', format: 'umd' },
+ ],
+ plugins: common
+}
diff --git a/rollup.config.react.js b/rollup.config.react.js
new file mode 100644
index 0000000..d4dcac7
--- /dev/null
+++ b/rollup.config.react.js
@@ -0,0 +1,9 @@
+import common from './rollup.common'
+
+export default {
+ input: `react.dev.js`,
+ output: [
+ { file: 'react.js', name: 'routletReact', format: 'umd' },
+ ],
+ plugins: common
+}