From 446431eed0576cab4b489297faeb027f6a702653 Mon Sep 17 00:00:00 2001 From: Sergey Date: Sat, 22 Dec 2018 11:52:13 +0200 Subject: [PATCH 1/4] Hyperapp#2 support --- package.json | 4 +-- src/Link.js | 66 +++++++++++++++++-------------------------------- src/Redirect.js | 6 ----- src/Route.js | 37 +++++++++++++++------------ src/Switch.js | 13 ++-------- src/history.js | 5 ++++ src/index.js | 10 ++++---- src/location.js | 61 ++++++++------------------------------------- 8 files changed, 68 insertions(+), 134 deletions(-) delete mode 100644 src/Redirect.js create mode 100644 src/history.js diff --git a/package.json b/package.json index 338ca23..21d883d 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "babel-jest": "^22.4.3", "babel-plugin-transform-react-jsx": "^6.24.1", "babel-preset-env": "^1.6.1", - "hyperapp": "^1.2.5", + "hyperapp": "^2.0.0", "jest": "^22.4.3", "prettier": "^1.11.1", "rollup": "^0.57.1", @@ -56,6 +56,6 @@ "typescript": "2.8.1" }, "peerDependencies": { - "hyperapp": "1.2.5" + "hyperapp": "2.0.0" } } diff --git a/src/Link.js b/src/Link.js index 6bf0499..4a33dc7 100644 --- a/src/Link.js +++ b/src/Link.js @@ -1,47 +1,27 @@ -import { h } from "hyperapp" +import { h } from 'hyperapp' +import { location } from './location' -function getOrigin(loc) { - return loc.protocol + "//" + loc.hostname + (loc.port ? ":" + loc.port : "") -} - -function isExternal(anchorElement) { - // Location.origin and HTMLAnchorElement.origin are not - // supported by IE and Safari. - return getOrigin(location) !== getOrigin(anchorElement) +function onClick(state, link, e){ + if( + e.defaultPrevented || + e.button !== 0 || + e.altKey || + e.metaKey || + e.ctrlKey || + e.shiftKey || + e.target.getAttribute("target") === "_blank" || + link.href === state.location.pathname + ){ + return state + } else { + e.preventDefault() + return location(state, link.href) + } } -export function Link(props, children) { - return function(state, actions) { - var to = props.to - var location = state.location - var onclick = props.onclick - delete props.to - delete props.location - - props.href = to - props.onclick = function(e) { - if (onclick) { - onclick(e) - } - if ( - e.defaultPrevented || - e.button !== 0 || - e.altKey || - e.metaKey || - e.ctrlKey || - e.shiftKey || - props.target === "_blank" || - isExternal(e.currentTarget) - ) { - } else { - e.preventDefault() - - if (to !== location.pathname) { - history.pushState(location.pathname, "", to) - } - } - } - - return h("a", props, children) - } +export function Link(props, childrens){ + props.onClick = props.onClick || [ onClick, props ] + props.href = props.to + delete props.to + return h('a', props, childrens) } diff --git a/src/Redirect.js b/src/Redirect.js deleted file mode 100644 index 39fed04..0000000 --- a/src/Redirect.js +++ /dev/null @@ -1,6 +0,0 @@ -export function Redirect(props) { - return function(state, actions) { - var location = state.location - history.replaceState(props.from || location.pathname, "", props.to) - } -} diff --git a/src/Route.js b/src/Route.js index c4a4ea5..8cb9c8d 100644 --- a/src/Route.js +++ b/src/Route.js @@ -1,18 +1,23 @@ -import { parseRoute } from "./parseRoute" +import { Switch } from './Switch' +import { parseRoute } from './parseRoute' -export function Route(props) { - return function(state, actions) { - var location = state.location - var match = parseRoute(props.path, location.pathname, { - exact: !props.parent - }) - - return ( - match && - props.render({ - match: match, - location: location +export function Route(props, childrens){ + var location = props.location + var path = props.path || '' + var render = props.render || Switch + var match = parseRoute(path, location.pathname, { + exact: !props.parent + }) + return (match && render( + function(){ + var args = Array.prototype.slice.call(arguments, 0) + args[0] = Object.assign({}, args[0], { + location: location, + path: path + args[0].path || '', }) - ) - } -} + return Route.apply(this, args) + }, + Object.assign({}, props, { match: match, location: location } ), + childrens) + ) +} \ No newline at end of file diff --git a/src/Switch.js b/src/Switch.js index fa84a5b..f87701e 100644 --- a/src/Switch.js +++ b/src/Switch.js @@ -1,12 +1,3 @@ -export function Switch(props, children) { - return function(state, actions) { - var child, - i = 0 - while ( - !(child = children[i] && children[i](state, actions)) && - i < children.length - ) - i++ - return child - } +export function Switch(props, childrens){ + return childrens[0] } diff --git a/src/history.js b/src/history.js new file mode 100644 index 0000000..6e0cc47 --- /dev/null +++ b/src/history.js @@ -0,0 +1,5 @@ +export function history(location){ + if(location && window.location.pathname !== location.pathname){ + window.history.pushState(location, '', location.pathname) + } +} \ No newline at end of file diff --git a/src/index.js b/src/index.js index 32ae51b..f538ace 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,5 @@ -export { Link } from "./Link" -export { Route } from "./Route" -export { Switch } from "./Switch" -export { Redirect } from "./Redirect" -export { location } from "./location" +export { location } from './location' +export { history } from './history' +export { Switch } from './Switch' +export { Route } from './Route' +export { Link } from './Link' diff --git a/src/location.js b/src/location.js index 1bd704e..7d427bf 100644 --- a/src/location.js +++ b/src/location.js @@ -1,51 +1,10 @@ -function wrapHistory(keys) { - return keys.reduce(function(next, key) { - var fn = history[key] - - history[key] = function(data, title, url) { - fn.call(this, data, title, url) - dispatchEvent(new CustomEvent("pushstate", { detail: data })) - } - - return function() { - history[key] = fn - next && next() - } - }, null) -} - -export var location = { - state: { - pathname: window.location.pathname, - previous: window.location.pathname - }, - actions: { - go: function(pathname) { - history.pushState(null, "", pathname) - }, - set: function(data) { - return data - } - }, - subscribe: function(actions) { - function handleLocationChange(e) { - actions.set({ - pathname: window.location.pathname, - previous: e.detail - ? (window.location.previous = e.detail) - : window.location.previous - }) - } - - var unwrap = wrapHistory(["pushState", "replaceState"]) - - addEventListener("pushstate", handleLocationChange) - addEventListener("popstate", handleLocationChange) - - return function() { - removeEventListener("pushstate", handleLocationChange) - removeEventListener("popstate", handleLocationChange) - unwrap() - } - } -} +export function location(state, path){ + return Object.assign({}, state, { + location: { + pathname: path || window.location.pathname, + previous: state && state.location + ? state.location.pathname + : window.location.pathname + } + }) +} \ No newline at end of file From 215cb525ceb7178c411cd06905a5ae162d4be729 Mon Sep 17 00:00:00 2001 From: Sergey Date: Mon, 7 Jan 2019 18:08:59 +0200 Subject: [PATCH 2/4] Hyperapp#V2 Router API Update - Path-to-regexp usage (with cached paths) - Router context passing - Redirect as subscription - Location 'back' and 'go' methods - Browser history as subscription --- package.json | 1 + src/Link.js | 26 +++++++++++------------- src/Redirect.js | 12 +++++++++++ src/Route.js | 51 ++++++++++++++++++++++++++++------------------- src/Switch.js | 3 --- src/history.js | 28 ++++++++++++++++++++++---- src/index.js | 4 ++-- src/location.js | 27 ++++++++++++++++--------- src/parseRoute.js | 46 ------------------------------------------ 9 files changed, 100 insertions(+), 98 deletions(-) create mode 100644 src/Redirect.js delete mode 100644 src/Switch.js delete mode 100644 src/parseRoute.js diff --git a/package.json b/package.json index 21d883d..1cdf270 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "babel-preset-env": "^1.6.1", "hyperapp": "^2.0.0", "jest": "^22.4.3", + "path-to-regexp": "^2.4.0", "prettier": "^1.11.1", "rollup": "^0.57.1", "uglify-js": "^3.3.16", diff --git a/src/Link.js b/src/Link.js index 4a33dc7..600f47c 100644 --- a/src/Link.js +++ b/src/Link.js @@ -1,27 +1,25 @@ import { h } from 'hyperapp' -import { location } from './location' +import { location } from './index' -function onClick(state, link, e){ - if( +const onClick = (state, link, e) => { + const { pathname } = state.location + if(!( e.defaultPrevented || e.button !== 0 || e.altKey || e.metaKey || e.ctrlKey || e.shiftKey || - e.target.getAttribute("target") === "_blank" || - link.href === state.location.pathname - ){ - return state - } else { + e.target.getAttribute('target') === '_blank' + )){ e.preventDefault() - return location(state, link.href) - } + return link.to !== pathname + ? [location, link.to] : state + } else return state } -export function Link(props, childrens){ +export const Link = (props, children) => { props.onClick = props.onClick || [ onClick, props ] props.href = props.to - delete props.to - return h('a', props, childrens) -} + return h('a', props, children) +} \ No newline at end of file diff --git a/src/Redirect.js b/src/Redirect.js new file mode 100644 index 0000000..55a1bc5 --- /dev/null +++ b/src/Redirect.js @@ -0,0 +1,12 @@ +import { location, Route } from './index' + +const fake = () => () => {} +export const Redirect = (props) => { + const { from, to } = props + const render = typeof to === 'function' ? to : () => to + const match = Route({...props, path: from, render }) + return { effect: match ? (props, dispatch) => { + dispatch([location, match]) + return () => {} + } : fake } +} \ No newline at end of file diff --git a/src/Route.js b/src/Route.js index 8cb9c8d..18070ea 100644 --- a/src/Route.js +++ b/src/Route.js @@ -1,23 +1,34 @@ -import { Switch } from './Switch' -import { parseRoute } from './parseRoute' +import pathToRegexp from 'path-to-regexp' -export function Route(props, childrens){ - var location = props.location - var path = props.path || '' - var render = props.render || Switch - var match = parseRoute(path, location.pathname, { - exact: !props.parent +const cache = {} +const compile = (path, { exact: end, strict }, keys = []) => { + const id = `${path}/${end}/${strict}` + return cache[id] ? cache[id] : (cache[id] = { + regexp: pathToRegexp(path, keys, { end, strict }), keys }) - return (match && render( - function(){ - var args = Array.prototype.slice.call(arguments, 0) - args[0] = Object.assign({}, args[0], { - location: location, - path: path + args[0].path || '', - }) - return Route.apply(this, args) - }, - Object.assign({}, props, { match: match, location: location } ), - childrens) - ) +} + +export const Route = (context, child) => { + const { + path = '', + exact = false, + strict = false, + render = () => child, + location = window.location + } = context + + const compiled = compile(path, { exact, strict }) + const match = compiled.regexp.exec(location.pathname) + const [ url, ...values ] = match || [] + return match ? render({ route: { + params: compiled.keys.reduce((params, key, index) => + Object.assign(params, {[ key.name ]: values[index]}), {}), + context, + path, + url + }}, (props, ...args) => Route.call(this, { + ...context, ...props, + render: props.render || undefined, + path: path + (props.path || ''), + }, ...args)) : null } \ No newline at end of file diff --git a/src/Switch.js b/src/Switch.js deleted file mode 100644 index f87701e..0000000 --- a/src/Switch.js +++ /dev/null @@ -1,3 +0,0 @@ -export function Switch(props, childrens){ - return childrens[0] -} diff --git a/src/history.js b/src/history.js index 6e0cc47..a0326ff 100644 --- a/src/history.js +++ b/src/history.js @@ -1,5 +1,25 @@ -export function history(location){ - if(location && window.location.pathname !== location.pathname){ - window.history.pushState(location, '', location.pathname) - } +import { location } from './index' + +const effect = (_, dispatch) => { + const handleLocationChange = () => + dispatch([ location, window.location.pathname ]) + addEventListener('popstate', handleLocationChange) + + return ['pushState', 'replaceState'].reduce((next, key) => { + const fn = history[key] + history[key] = (data, tittle, url) => { + !data.ignore && dispatch([location, url]) + return fn.call(history, data, tittle, url) + } + return () => { + history[key] = fn + next() + } + }, () => removeEventListener("popstate", handleLocationChange)) +} + +export default location => { + if(location && window.location.pathname !== location.pathname) + history.pushState({ location, ignore: true }, '', location.pathname) + return { effect } } \ No newline at end of file diff --git a/src/index.js b/src/index.js index f538ace..4f903b6 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,5 @@ export { location } from './location' -export { history } from './history' -export { Switch } from './Switch' +export { default as history } from './history' +export { Redirect } from './Redirect' export { Route } from './Route' export { Link } from './Link' diff --git a/src/location.js b/src/location.js index 7d427bf..a9d3c9a 100644 --- a/src/location.js +++ b/src/location.js @@ -1,10 +1,19 @@ -export function location(state, path){ - return Object.assign({}, state, { - location: { - pathname: path || window.location.pathname, - previous: state && state.location - ? state.location.pathname - : window.location.pathname - } - }) +export const location = (state, path) => ({ + ...state, + location: { + pathname: path, + previous: state && state.location ? state.location : null + } +}) + +location.back = (state, n = 1) => ({ + ...state, + location: new Array(n).fill(n).reduce(location => + location.previous || location, state.location) +}) + +location.go = url => (state, event) => { + event.preventDefault() + event.stopPropagation() + return [location, url] } \ No newline at end of file diff --git a/src/parseRoute.js b/src/parseRoute.js deleted file mode 100644 index 41dfe09..0000000 --- a/src/parseRoute.js +++ /dev/null @@ -1,46 +0,0 @@ -function createMatch(isExact, path, url, params) { - return { - isExact: isExact, - path: path, - url: url, - params: params - } -} - -function trimTrailingSlash(url) { - for (var len = url.length; "/" === url[--len]; ); - return url.slice(0, len + 1) -} - -function decodeParam(val) { - try { - return decodeURIComponent(val) - } catch (e) { - return val - } -} - -export function parseRoute(path, url, options) { - if (path === url || !path) { - return createMatch(path === url, path, url) - } - - var exact = options && options.exact - var paths = trimTrailingSlash(path).split("/") - var urls = trimTrailingSlash(url).split("/") - - if (paths.length > urls.length || (exact && paths.length < urls.length)) { - return - } - - for (var i = 0, params = {}, len = paths.length, url = ""; i < len; i++) { - if (":" === paths[i][0]) { - params[paths[i].slice(1)] = urls[i] = decodeParam(urls[i]) - } else if (paths[i] !== urls[i]) { - return - } - url += urls[i] + "/" - } - - return createMatch(false, path, url.slice(0, -1), params) -} From 2fa325dabf8b0dfae6cc5fee7909deb90d235066 Mon Sep 17 00:00:00 2001 From: Sergey Date: Tue, 8 Jan 2019 00:17:10 +0200 Subject: [PATCH 3/4] Chore: Parcel added Removed Rollup because it requires configuration and usage of multiple plugins (resolver, commonjs) --- .gitignore | 1 + package.json | 25 ++++++++++++------------- src/Route.js | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 98a3c17..9d3ab8e 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,5 @@ coverage # Dist *.gz +.cache dist/ diff --git a/package.json b/package.json index 1cdf270..eb4f277 100644 --- a/package.json +++ b/package.json @@ -21,39 +21,38 @@ ], "scripts": { "test": "jest --coverage --no-cache&& tsc -p test/ts", - "build": "npm run bundle && npm run minify", - "bundle": "rollup -i src/index.js -o dist/router.js -f umd -mn hyperappRouter -g hyperapp:hyperapp", - "minify": "uglifyjs dist/router.js -o dist/router.js -mc pure_funcs=['Object.defineProperty'] --source-map includeSources,url=router.js.map", + "build": "parcel build ./src/index.js --out-dir dist --out-file router.js --global hyperappRouter --no-autoinstall", "prepublish": "npm run build", + "prepare": "npm run build", "format": "prettier --semi false --write '{src,test}/**/*.js'", "release": "npm run build && npm test && git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish" }, "babel": { "presets": [ - "env" - ], - "plugins": [ [ - "transform-react-jsx", + "@babel/preset-env", { - "pragma": "h" + "forceAllTransforms": true } ] + ], + "plugins": [ + "@babel/plugin-transform-runtime" ] }, "jest": { "testURL": "http://localhost" }, "devDependencies": { + "@babel/core": "^7.2.2", + "@babel/plugin-transform-runtime": "^7.2.0", + "@babel/preset-env": "^7.2.3", "babel-jest": "^22.4.3", - "babel-plugin-transform-react-jsx": "^6.24.1", - "babel-preset-env": "^1.6.1", - "hyperapp": "^2.0.0", + "hyperapp": "github:jorgebucaran/hyperapp#V2", "jest": "^22.4.3", + "parcel-bundler": "^1.11.0", "path-to-regexp": "^2.4.0", "prettier": "^1.11.1", - "rollup": "^0.57.1", - "uglify-js": "^3.3.16", "typescript": "2.8.1" }, "peerDependencies": { diff --git a/src/Route.js b/src/Route.js index 18070ea..dfd1844 100644 --- a/src/Route.js +++ b/src/Route.js @@ -26,7 +26,7 @@ export const Route = (context, child) => { context, path, url - }}, (props, ...args) => Route.call(this, { + }}, (props, ...args) => Route.call(undefined, { ...context, ...props, render: props.render || undefined, path: path + (props.path || ''), From ebb611befe1a9940dacc83c15ce296d1bf2bccf5 Mon Sep 17 00:00:00 2001 From: Vladimir Kutepov Date: Mon, 4 Mar 2019 13:05:46 +0200 Subject: [PATCH 4/4] Update src/Link.js Co-Authored-By: sergey-shpak --- src/Link.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Link.js b/src/Link.js index 600f47c..ea2870a 100644 --- a/src/Link.js +++ b/src/Link.js @@ -10,7 +10,7 @@ const onClick = (state, link, e) => { e.metaKey || e.ctrlKey || e.shiftKey || - e.target.getAttribute('target') === '_blank' + e.currentTarget.getAttribute('target') === '_blank' )){ e.preventDefault() return link.to !== pathname @@ -22,4 +22,4 @@ export const Link = (props, children) => { props.onClick = props.onClick || [ onClick, props ] props.href = props.to return h('a', props, children) -} \ No newline at end of file +}