mirror of
https://github.com/metosin/reitit.git
synced 2025-12-18 08:51:12 +00:00
Merge pull request #62 from metosin/frontend-routing
Frontend routing implementation (WIP)
This commit is contained in:
commit
93d911d60e
44 changed files with 855 additions and 46 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,4 +1,4 @@
|
||||||
/target
|
target/
|
||||||
/classes
|
/classes
|
||||||
/checkouts
|
/checkouts
|
||||||
pom.xml
|
pom.xml
|
||||||
|
|
@ -11,3 +11,4 @@ pom.xml.asc
|
||||||
/gh-pages
|
/gh-pages
|
||||||
/node_modules
|
/node_modules
|
||||||
/_book
|
/_book
|
||||||
|
figwheel_server.log
|
||||||
|
|
|
||||||
14
README.md
14
README.md
|
|
@ -36,18 +36,18 @@ Bubblin' under:
|
||||||
All bundled:
|
All bundled:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/reitit "0.1.3"]
|
[metosin/reitit "0.1.4-SNAPSHOT"]
|
||||||
```
|
```
|
||||||
|
|
||||||
Optionally, the parts can be required separately:
|
Optionally, the parts can be required separately:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/reitit-core "0.1.3"]
|
[metosin/reitit-core "0.1.4-SNAPSHOT"]
|
||||||
[metosin/reitit-ring "0.1.3"]
|
[metosin/reitit-ring "0.1.4-SNAPSHOT"]
|
||||||
[metosin/reitit-spec "0.1.3"]
|
[metosin/reitit-spec "0.1.4-SNAPSHOT"]
|
||||||
[metosin/reitit-schema "0.1.3"]
|
[metosin/reitit-schema "0.1.4-SNAPSHOT"]
|
||||||
[metosin/reitit-swagger "0.1.3"]
|
[metosin/reitit-swagger "0.1.4-SNAPSHOT"]
|
||||||
[metosin/reitit-swagger-ui "0.1.3"]
|
[metosin/reitit-swagger-ui "0.1.4-SNAPSHOT"]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
* Extendable
|
* Extendable
|
||||||
* Modular
|
* Modular
|
||||||
* [Fast](performance.md)
|
* [Fast](performance.md)
|
||||||
|
* [Frontend routing](./frontend/README.md)
|
||||||
|
|
||||||
Modules:
|
Modules:
|
||||||
|
|
||||||
|
|
@ -19,22 +20,24 @@ Modules:
|
||||||
* `reitit-schema` [Schema](https://github.com/plumatic/schema) coercion
|
* `reitit-schema` [Schema](https://github.com/plumatic/schema) coercion
|
||||||
* `reitit-swagger` [Swagger2](https://swagger.io/) apidocs
|
* `reitit-swagger` [Swagger2](https://swagger.io/) apidocs
|
||||||
* `reitit-swagger-ui` Integrated [Swagger UI](https://github.com/swagger-api/swagger-ui).
|
* `reitit-swagger-ui` Integrated [Swagger UI](https://github.com/swagger-api/swagger-ui).
|
||||||
|
* `reitit-frontend` Tools for frontend routing.
|
||||||
|
|
||||||
To use Reitit, add the following dependecy to your project:
|
To use Reitit, add the following dependency to your project:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/reitit "0.1.3"]
|
[metosin/reitit "0.1.4-SNAPSHOT"]
|
||||||
```
|
```
|
||||||
|
|
||||||
Optionally, the parts can be required separately:
|
Optionally, the parts can be required separately:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/reitit-core "0.1.3"]
|
[metosin/reitit-core "0.1.4-SNAPSHOT"]
|
||||||
[metosin/reitit-ring "0.1.3"]
|
[metosin/reitit-ring "0.1.4-SNAPSHOT"]
|
||||||
[metosin/reitit-spec "0.1.3"]
|
[metosin/reitit-spec "0.1.4-SNAPSHOT"]
|
||||||
[metosin/reitit-schema "0.1.3"]
|
[metosin/reitit-schema "0.1.4-SNAPSHOT"]
|
||||||
[metosin/reitit-swagger "0.1.3"]
|
[metosin/reitit-swagger "0.1.4-SNAPSHOT"]
|
||||||
[metosin/reitit-swagger-ui "0.1.3"]
|
[metosin/reitit-swagger-ui "0.1.4-SNAPSHOT"]
|
||||||
|
[metosin/frontend "0.1.4-SNAPSHOT"]
|
||||||
```
|
```
|
||||||
|
|
||||||
For discussions, there is a [#reitit](https://clojurians.slack.com/messages/reitit/) channel in [Clojurians slack](http://clojurians.net/).
|
For discussions, there is a [#reitit](https://clojurians.slack.com/messages/reitit/) channel in [Clojurians slack](http://clojurians.net/).
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,11 @@
|
||||||
{:file "doc/ring/route_data_validation.md"}]
|
{:file "doc/ring/route_data_validation.md"}]
|
||||||
["Compiling Middleware" {:file "doc/ring/compiling_middleware.md"}]
|
["Compiling Middleware" {:file "doc/ring/compiling_middleware.md"}]
|
||||||
["Swagger Support" {:file "doc/ring/swagger.md"}]]
|
["Swagger Support" {:file "doc/ring/swagger.md"}]]
|
||||||
|
["Frontend"
|
||||||
|
{:file "doc/frontend/README.md"}
|
||||||
|
["Basics" {:file "doc/frontend/basics.md"}]
|
||||||
|
["Browser integration" {:file "doc/frontend/browser.md"}]
|
||||||
|
["Controllers" {:file "doc/frontend/controllers/.md"}]]
|
||||||
["Performance" {:file "doc/performance.md"}]
|
["Performance" {:file "doc/performance.md"}]
|
||||||
["Interceptors (WIP)" {:file "doc/interceptors.md"}]
|
["Interceptors (WIP)" {:file "doc/interceptors.md"}]
|
||||||
["Development Instructions" {:file "doc/development.md"}]
|
["Development Instructions" {:file "doc/development.md"}]
|
||||||
|
|
|
||||||
5
doc/frontend/README.md
Normal file
5
doc/frontend/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Frontend
|
||||||
|
|
||||||
|
* [Basics](basics.md)
|
||||||
|
* [Browser integration](browser.md)
|
||||||
|
* [Controllers](controllers.md)
|
||||||
3
doc/frontend/basics.md
Normal file
3
doc/frontend/basics.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Frontend basics
|
||||||
|
|
||||||
|
TODO
|
||||||
3
doc/frontend/browser.md
Normal file
3
doc/frontend/browser.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Frontend browser integration
|
||||||
|
|
||||||
|
TODO
|
||||||
3
doc/frontend/controllers.md
Normal file
3
doc/frontend/controllers.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Controllers
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
[Ring](https://github.com/ring-clojure/ring) is a Clojure web applications library inspired by Python's WSGI and Ruby's Rack. By abstracting the details of HTTP into a simple, unified API, Ring allows web applications to be constructed of modular components that can be shared among a variety of applications, web servers, and web frameworks.
|
[Ring](https://github.com/ring-clojure/ring) is a Clojure web applications library inspired by Python's WSGI and Ruby's Rack. By abstracting the details of HTTP into a simple, unified API, Ring allows web applications to be constructed of modular components that can be shared among a variety of applications, web servers, and web frameworks.
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/reitit-ring "0.1.3"]
|
[metosin/reitit-ring "0.1.4-SNAPSHOT"]
|
||||||
```
|
```
|
||||||
|
|
||||||
Ring-router adds support for [handlers](https://github.com/ring-clojure/ring/wiki/Concepts#handlers), [middleware](https://github.com/ring-clojure/ring/wiki/Concepts#middleware) and routing based on `:request-method`. Ring-router is created with `reitit.ring/router` function. It uses a custom route compiler, creating a optimized data structure for handling route matches, with compiled middleware chain & handlers for all request methods. It also ensures that all routes have a `:handler` defined. `reitit.ring/ring-handler` is used to create a Ring handler out of ring-router.
|
Ring-router adds support for [handlers](https://github.com/ring-clojure/ring/wiki/Concepts#handlers), [middleware](https://github.com/ring-clojure/ring/wiki/Concepts#middleware) and routing based on `:request-method`. Ring-router is created with `reitit.ring/router` function. It uses a custom route compiler, creating a optimized data structure for handling route matches, with compiled middleware chain & handlers for all request methods. It also ensures that all routes have a `:handler` defined. `reitit.ring/ring-handler` is used to create a Ring handler out of ring-router.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# Swagger Support
|
# Swagger Support
|
||||||
|
|
||||||
```
|
```
|
||||||
[metosin/reitit-swagger "0.1.3"]
|
[metosin/reitit-swagger "0.1.4-SNAPSHOT"]
|
||||||
```
|
```
|
||||||
|
|
||||||
Reitit supports [Swagger2](https://swagger.io/) documentation, thanks to [schema-tools](https://github.com/metosin/schema-tools) and [spec-tools](https://github.com/metosin/spec-tools). Documentation is extracted from route definitions, coercion `:parameters` and `:responses` and from a set of new documentation keys.
|
Reitit supports [Swagger2](https://swagger.io/) documentation, thanks to [schema-tools](https://github.com/metosin/schema-tools) and [spec-tools](https://github.com/metosin/spec-tools). Documentation is extracted from route definitions, coercion `:parameters` and `:responses` and from a set of new documentation keys.
|
||||||
|
|
@ -44,7 +44,7 @@ If you need to post-process the generated spec, just wrap the handler with a cus
|
||||||
[Swagger-ui](https://github.com/swagger-api/swagger-ui) is a user interface to visualize and interact with the Swagger specification. To make things easy, there is a pre-integrated version of the swagger-ui as a separate module.
|
[Swagger-ui](https://github.com/swagger-api/swagger-ui) is a user interface to visualize and interact with the Swagger specification. To make things easy, there is a pre-integrated version of the swagger-ui as a separate module.
|
||||||
|
|
||||||
```
|
```
|
||||||
[metosin/reitit-swagger-ui "0.1.3"]
|
[metosin/reitit-swagger-ui "0.1.4-SNAPSHOT"]
|
||||||
```
|
```
|
||||||
|
|
||||||
`reitit.swagger-ui/create-swagger-ui-hander` can be used to create a ring-handler to serve the swagger-ui. It accepts the following options:
|
`reitit.swagger-ui/create-swagger-ui-hander` can be used to create a ring-handler to serve the swagger-ui. It accepts the following options:
|
||||||
|
|
|
||||||
1
examples/frontend-controllers/checkouts/reitit-core
Symbolic link
1
examples/frontend-controllers/checkouts/reitit-core
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../../../modules/reitit-core
|
||||||
1
examples/frontend-controllers/checkouts/reitit-frontend
Symbolic link
1
examples/frontend-controllers/checkouts/reitit-frontend
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../../../modules/reitit-frontend
|
||||||
1
examples/frontend-controllers/checkouts/reitit-schema
Symbolic link
1
examples/frontend-controllers/checkouts/reitit-schema
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../../../modules/reitit-schema
|
||||||
52
examples/frontend-controllers/project.clj
Normal file
52
examples/frontend-controllers/project.clj
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
(defproject frontend "0.1.0-SNAPSHOT"
|
||||||
|
:description "FIXME: write description"
|
||||||
|
:url "http://example.com/FIXME"
|
||||||
|
:license {:name "Eclipse Public License"
|
||||||
|
:url "http://www.eclipse.org/legal/epl-v10.html"}
|
||||||
|
|
||||||
|
:dependencies [[org.clojure/clojure "1.9.0"]
|
||||||
|
[ring-server "0.5.0"]
|
||||||
|
[reagent "0.8.1"]
|
||||||
|
[ring "1.6.3"]
|
||||||
|
[compojure "1.6.1"]
|
||||||
|
[hiccup "1.0.5"]
|
||||||
|
[org.clojure/clojurescript "1.10.339"]
|
||||||
|
[metosin/reitit "0.1.4-SNAPSHOT"]
|
||||||
|
[metosin/reitit-schema "0.1.4-SNAPSHOT"]
|
||||||
|
[metosin/reitit-frontend "0.1.4-SNAPSHOT"]
|
||||||
|
;; Just for pretty printting the match
|
||||||
|
[fipp "0.6.12"]]
|
||||||
|
|
||||||
|
:plugins [[lein-cljsbuild "1.1.7"]
|
||||||
|
[lein-figwheel "0.5.16"]]
|
||||||
|
|
||||||
|
:source-paths []
|
||||||
|
:resource-paths ["resources" "target/cljsbuild"]
|
||||||
|
|
||||||
|
:profiles {:dev {:dependencies [[binaryage/devtools "0.9.10"]]}}
|
||||||
|
|
||||||
|
:cljsbuild
|
||||||
|
{:builds
|
||||||
|
[{:id "app"
|
||||||
|
:figwheel true
|
||||||
|
:source-paths ["src"]
|
||||||
|
:watch-paths ["src" "checkouts/reitit-frontend/src"]
|
||||||
|
:compiler {:main "frontend.core"
|
||||||
|
:asset-path "/js/out"
|
||||||
|
:output-to "target/cljsbuild/public/js/app.js"
|
||||||
|
:output-dir "target/cljsbuild/public/js/out"
|
||||||
|
:source-map true
|
||||||
|
:optimizations :none
|
||||||
|
:pretty-print true
|
||||||
|
:preloads [devtools.preload]}}
|
||||||
|
{:id "min"
|
||||||
|
:source-paths ["src"]
|
||||||
|
:compiler {:output-to "target/cljsbuild/public/js/app.js"
|
||||||
|
:output-dir "target/cljsbuild/public/js"
|
||||||
|
:source-map "target/cljsbuild/public/js/app.js.map"
|
||||||
|
:optimizations :advanced
|
||||||
|
:pretty-print false}}]}
|
||||||
|
|
||||||
|
:figwheel {:http-server-root "public"
|
||||||
|
:server-port 3449
|
||||||
|
:nrepl-port 7002})
|
||||||
10
examples/frontend-controllers/resources/public/index.html
Normal file
10
examples/frontend-controllers/resources/public/index.html
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Reitit frontend example</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script src="js/app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
95
examples/frontend-controllers/src/frontend/core.cljs
Normal file
95
examples/frontend-controllers/src/frontend/core.cljs
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
(ns frontend.core
|
||||||
|
(:require [reagent.core :as r]
|
||||||
|
[reitit.core :as re]
|
||||||
|
[reitit.frontend :as rf]
|
||||||
|
[reitit.frontend.history :as rfh]
|
||||||
|
[reitit.frontend.controllers :as rfc]
|
||||||
|
[reitit.coercion :as rc]
|
||||||
|
[reitit.coercion.schema :as rsc]
|
||||||
|
[schema.core :as s]
|
||||||
|
[fipp.edn :as fedn]))
|
||||||
|
|
||||||
|
(defonce history (atom nil))
|
||||||
|
|
||||||
|
(defn home-page []
|
||||||
|
[:div
|
||||||
|
[:h2 "Welcome to frontend"]
|
||||||
|
[:p "Look at console log for controller calls."]])
|
||||||
|
|
||||||
|
(defn item-page [match]
|
||||||
|
(let [{:keys [path query]} (:parameters match)
|
||||||
|
{:keys [id]} path]
|
||||||
|
[:div
|
||||||
|
[:ul
|
||||||
|
[:li [:a {:href (rfh/href @history ::item {:id 1})} "Item 1"]]
|
||||||
|
[:li [:a {:href (rfh/href @history ::item {:id 2} {:foo "bar"})} "Item 2"]]]
|
||||||
|
(if id
|
||||||
|
[:h2 "Selected item " id])
|
||||||
|
(if (:foo query)
|
||||||
|
[:p "Optional foo query param: " (:foo query)])]))
|
||||||
|
|
||||||
|
(defonce match (r/atom nil))
|
||||||
|
|
||||||
|
(defn current-page []
|
||||||
|
[:div
|
||||||
|
[:ul
|
||||||
|
[:li [:a {:href (rfh/href @history ::frontpage)} "Frontpage"]]
|
||||||
|
[:li
|
||||||
|
[:a {:href (rfh/href @history ::item-list)} "Item list"]
|
||||||
|
]]
|
||||||
|
(if @match
|
||||||
|
(let [view (:view (:data @match))]
|
||||||
|
[view @match]))
|
||||||
|
[:pre (with-out-str (fedn/pprint @match))]])
|
||||||
|
|
||||||
|
(defn log-fn [& params]
|
||||||
|
(fn [_]
|
||||||
|
(apply js/console.log params)))
|
||||||
|
|
||||||
|
(def routes
|
||||||
|
(re/router
|
||||||
|
["/"
|
||||||
|
[""
|
||||||
|
{:name ::frontpage
|
||||||
|
:view home-page
|
||||||
|
:controllers [{:start (log-fn "start" "frontpage controller")
|
||||||
|
:stop (log-fn "stop" "frontpage controller")}]}]
|
||||||
|
["items"
|
||||||
|
;; Shared data for sub-routes
|
||||||
|
{:view item-page
|
||||||
|
:controllers [{:start (log-fn "start" "items controller")
|
||||||
|
:stop (log-fn "stop" "items controller")}]}
|
||||||
|
|
||||||
|
[""
|
||||||
|
{:name ::item-list
|
||||||
|
:controllers [{:start (log-fn "start" "item-list controller")
|
||||||
|
:stop (log-fn "stop" "item-list controller")}]}]
|
||||||
|
["/:id"
|
||||||
|
{:name ::item
|
||||||
|
:parameters {:path {:id s/Int}
|
||||||
|
:query {(s/optional-key :foo) s/Keyword}}
|
||||||
|
:controllers [{:params (fn [match]
|
||||||
|
(:path (:parameters match)))
|
||||||
|
:start (fn [params]
|
||||||
|
(js/console.log "start" "item controller" (:id params)))
|
||||||
|
:stop (fn [params]
|
||||||
|
(js/console.log "stop" "item controller" (:id params)))}]}]]]
|
||||||
|
{:compile rc/compile-request-coercers
|
||||||
|
:data {:controllers [{:start (log-fn "start" "root-controller")
|
||||||
|
:stop (log-fn "stop" "root controller")}]
|
||||||
|
:coercion rsc/coercion}}))
|
||||||
|
|
||||||
|
(defn init! []
|
||||||
|
(swap! history (fn [old-history]
|
||||||
|
(rfh/stop! old-history)
|
||||||
|
(rfh/start!
|
||||||
|
routes
|
||||||
|
(fn [new-match]
|
||||||
|
(swap! match (fn [old-match]
|
||||||
|
(if new-match
|
||||||
|
(assoc new-match :controllers (rfc/apply-controllers (:controllers old-match) new-match))))))
|
||||||
|
{:use-fragment true
|
||||||
|
:path-prefix "/"})))
|
||||||
|
(r/render [current-page] (.getElementById js/document "app")))
|
||||||
|
|
||||||
|
(init!)
|
||||||
1
examples/frontend/checkouts/reitit-core
Symbolic link
1
examples/frontend/checkouts/reitit-core
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../../../modules/reitit-core
|
||||||
1
examples/frontend/checkouts/reitit-frontend
Symbolic link
1
examples/frontend/checkouts/reitit-frontend
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../../../modules/reitit-frontend
|
||||||
1
examples/frontend/checkouts/reitit-schema
Symbolic link
1
examples/frontend/checkouts/reitit-schema
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../../../modules/reitit-schema
|
||||||
52
examples/frontend/project.clj
Normal file
52
examples/frontend/project.clj
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
(defproject frontend "0.1.0-SNAPSHOT"
|
||||||
|
:description "FIXME: write description"
|
||||||
|
:url "http://example.com/FIXME"
|
||||||
|
:license {:name "Eclipse Public License"
|
||||||
|
:url "http://www.eclipse.org/legal/epl-v10.html"}
|
||||||
|
|
||||||
|
:dependencies [[org.clojure/clojure "1.9.0"]
|
||||||
|
[ring-server "0.5.0"]
|
||||||
|
[reagent "0.8.1"]
|
||||||
|
[ring "1.6.3"]
|
||||||
|
[compojure "1.6.1"]
|
||||||
|
[hiccup "1.0.5"]
|
||||||
|
[org.clojure/clojurescript "1.10.339"]
|
||||||
|
[metosin/reitit "0.1.4-SNAPSHOT"]
|
||||||
|
[metosin/reitit-schema "0.1.4-SNAPSHOT"]
|
||||||
|
[metosin/reitit-frontend "0.1.4-SNAPSHOT"]
|
||||||
|
;; Just for pretty printting the match
|
||||||
|
[fipp "0.6.12"]]
|
||||||
|
|
||||||
|
:plugins [[lein-cljsbuild "1.1.7"]
|
||||||
|
[lein-figwheel "0.5.16"]]
|
||||||
|
|
||||||
|
:source-paths []
|
||||||
|
:resource-paths ["resources" "target/cljsbuild"]
|
||||||
|
|
||||||
|
:profiles {:dev {:dependencies [[binaryage/devtools "0.9.10"]]}}
|
||||||
|
|
||||||
|
:cljsbuild
|
||||||
|
{:builds
|
||||||
|
[{:id "app"
|
||||||
|
:figwheel true
|
||||||
|
:source-paths ["src"]
|
||||||
|
:watch-paths ["src" "checkouts/reitit-frontend/src"]
|
||||||
|
:compiler {:main "frontend.core"
|
||||||
|
:asset-path "/js/out"
|
||||||
|
:output-to "target/cljsbuild/public/js/app.js"
|
||||||
|
:output-dir "target/cljsbuild/public/js/out"
|
||||||
|
:source-map true
|
||||||
|
:optimizations :none
|
||||||
|
:pretty-print true
|
||||||
|
:preloads [devtools.preload]}}
|
||||||
|
{:id "min"
|
||||||
|
:source-paths ["src"]
|
||||||
|
:compiler {:output-to "target/cljsbuild/public/js/app.js"
|
||||||
|
:output-dir "target/cljsbuild/public/js"
|
||||||
|
:source-map "target/cljsbuild/public/js/app.js.map"
|
||||||
|
:optimizations :advanced
|
||||||
|
:pretty-print false}}]}
|
||||||
|
|
||||||
|
:figwheel {:http-server-root "public"
|
||||||
|
:server-port 3449
|
||||||
|
:nrepl-port 7002})
|
||||||
10
examples/frontend/resources/public/index.html
Normal file
10
examples/frontend/resources/public/index.html
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Reitit frontend example</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script src="js/app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
73
examples/frontend/src/frontend/core.cljs
Normal file
73
examples/frontend/src/frontend/core.cljs
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
(ns frontend.core
|
||||||
|
(:require [reagent.core :as r]
|
||||||
|
[reitit.core :as re]
|
||||||
|
[reitit.frontend :as rf]
|
||||||
|
[reitit.frontend.history :as rfh]
|
||||||
|
[reitit.coercion :as rc]
|
||||||
|
[reitit.coercion.schema :as rsc]
|
||||||
|
[schema.core :as s]
|
||||||
|
[fipp.edn :as fedn]))
|
||||||
|
|
||||||
|
(defonce history (atom nil))
|
||||||
|
|
||||||
|
(defn home-page []
|
||||||
|
[:div
|
||||||
|
[:h2 "Welcome to frontend"]])
|
||||||
|
|
||||||
|
(defn about-page []
|
||||||
|
[:div
|
||||||
|
[:h2 "About frontend"]
|
||||||
|
[:ul
|
||||||
|
[:li [:a {:href "http://google.com"} "external link"]]
|
||||||
|
[:li [:a {:href (rfh/href @history ::foobar)} "Missing route"]]
|
||||||
|
[:li [:a {:href (rfh/href @history ::item)} "Missing route params"]]]])
|
||||||
|
|
||||||
|
(defn item-page [match]
|
||||||
|
(let [{:keys [path query]} (:parameters match)
|
||||||
|
{:keys [id]} path]
|
||||||
|
[:div
|
||||||
|
[:h2 "Selected item " id]
|
||||||
|
(if (:foo query)
|
||||||
|
[:p "Optional foo query param: " (:foo query)])]))
|
||||||
|
|
||||||
|
(defonce match (r/atom nil))
|
||||||
|
|
||||||
|
(defn current-page []
|
||||||
|
[:div
|
||||||
|
[:ul
|
||||||
|
[:li [:a {:href (rfh/href @history ::frontpage)} "Frontpage"]]
|
||||||
|
[:li [:a {:href (rfh/href @history ::about)} "About"]]
|
||||||
|
[:li [:a {:href (rfh/href @history ::item {:id 1})} "Item 1"]]
|
||||||
|
[:li [:a {:href (rfh/href @history ::item {:id 2} {:foo "bar"})} "Item 2"]]]
|
||||||
|
(if @match
|
||||||
|
(let [view (:view (:data @match))]
|
||||||
|
[view @match]))
|
||||||
|
[:pre (with-out-str (fedn/pprint @match))]])
|
||||||
|
|
||||||
|
(def routes
|
||||||
|
(re/router
|
||||||
|
["/"
|
||||||
|
[""
|
||||||
|
{:name ::frontpage
|
||||||
|
:view home-page}]
|
||||||
|
["about"
|
||||||
|
{:name ::about
|
||||||
|
:view about-page}]
|
||||||
|
["item/:id"
|
||||||
|
{:name ::item
|
||||||
|
:view item-page
|
||||||
|
:parameters {:path {:id s/Int}
|
||||||
|
:query {(s/optional-key :foo) s/Keyword}}}]]
|
||||||
|
{:compile rc/compile-request-coercers
|
||||||
|
:data {:coercion rsc/coercion}}))
|
||||||
|
|
||||||
|
(defn init! []
|
||||||
|
(swap! history (fn [old-history]
|
||||||
|
(rfh/stop! old-history)
|
||||||
|
(rfh/start! routes
|
||||||
|
(fn [m] (reset! match m))
|
||||||
|
{:use-fragment true
|
||||||
|
:path-prefix "/"})))
|
||||||
|
(r/render [current-page] (.getElementById js/document "app")))
|
||||||
|
|
||||||
|
(init!)
|
||||||
|
|
@ -3,4 +3,4 @@
|
||||||
:dependencies [[org.clojure/clojure "1.9.0"]
|
:dependencies [[org.clojure/clojure "1.9.0"]
|
||||||
[ring "1.6.3"]
|
[ring "1.6.3"]
|
||||||
[metosin/muuntaja "0.4.1"]
|
[metosin/muuntaja "0.4.1"]
|
||||||
[metosin/reitit "0.1.3"]])
|
[metosin/reitit "0.1.4-SNAPSHOT"]])
|
||||||
|
|
|
||||||
|
|
@ -3,4 +3,4 @@
|
||||||
:dependencies [[org.clojure/clojure "1.9.0"]
|
:dependencies [[org.clojure/clojure "1.9.0"]
|
||||||
[ring "1.6.3"]
|
[ring "1.6.3"]
|
||||||
[metosin/muuntaja "0.4.1"]
|
[metosin/muuntaja "0.4.1"]
|
||||||
[metosin/reitit "0.1.3"]])
|
[metosin/reitit "0.1.4-SNAPSHOT"]])
|
||||||
|
|
|
||||||
|
|
@ -3,5 +3,5 @@
|
||||||
:dependencies [[org.clojure/clojure "1.9.0"]
|
:dependencies [[org.clojure/clojure "1.9.0"]
|
||||||
[ring "1.6.3"]
|
[ring "1.6.3"]
|
||||||
[metosin/muuntaja "0.5.0"]
|
[metosin/muuntaja "0.5.0"]
|
||||||
[metosin/reitit "0.1.3"]]
|
[metosin/reitit "0.1.4-SNAPSHOT"]]
|
||||||
:repl-options {:init-ns example.server})
|
:repl-options {:init-ns example.server})
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
(defproject metosin/reitit-core "0.1.3"
|
(defproject metosin/reitit-core "0.1.4-SNAPSHOT"
|
||||||
:description "Snappy data-driven router for Clojure(Script)"
|
:description "Snappy data-driven router for Clojure(Script)"
|
||||||
:url "https://github.com/metosin/reitit"
|
:url "https://github.com/metosin/reitit"
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,10 @@
|
||||||
(defn path-for [^Route route path-params]
|
(defn path-for [^Route route path-params]
|
||||||
(if-let [required (:path-params route)]
|
(if-let [required (:path-params route)]
|
||||||
(if (every? #(contains? path-params %) required)
|
(if (every? #(contains? path-params %) required)
|
||||||
(str "/" (str/join \/ (map #(get (or path-params {}) % %) (:path-parts route)))))
|
(->> (:path-parts route)
|
||||||
|
(map #(get (or path-params {}) % %))
|
||||||
|
(str/join \/)
|
||||||
|
(str "/")))
|
||||||
(:path route)))
|
(:path route)))
|
||||||
|
|
||||||
(defn throw-on-missing-path-params [template required path-params]
|
(defn throw-on-missing-path-params [template required path-params]
|
||||||
|
|
@ -214,5 +217,8 @@
|
||||||
"shallow transform of query parameters into query string"
|
"shallow transform of query parameters into query string"
|
||||||
[params]
|
[params]
|
||||||
(->> params
|
(->> params
|
||||||
(map (fn [[k v]] (str (url-encode (into-string k)) "=" (url-encode (into-string v)))))
|
(map (fn [[k v]]
|
||||||
|
(str (url-encode (into-string k))
|
||||||
|
"="
|
||||||
|
(url-encode (into-string v)))))
|
||||||
(str/join "&")))
|
(str/join "&")))
|
||||||
|
|
|
||||||
9
modules/reitit-frontend/project.clj
Normal file
9
modules/reitit-frontend/project.clj
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
(defproject metosin/reitit-frontend "0.1.4-SNAPSHOT"
|
||||||
|
:description "Reitit: Clojurescript frontend routing core"
|
||||||
|
:url "https://github.com/metosin/reitit"
|
||||||
|
:license {:name "Eclipse Public License"
|
||||||
|
:url "http://www.eclipse.org/legal/epl-v10.html"}
|
||||||
|
:plugins [[lein-parent "0.3.2"]]
|
||||||
|
:parent-project {:path "../../project.clj"
|
||||||
|
:inherit [:deploy-repositories :managed-dependencies]}
|
||||||
|
:dependencies [[metosin/reitit-core]])
|
||||||
63
modules/reitit-frontend/src/reitit/frontend.cljs
Normal file
63
modules/reitit-frontend/src/reitit/frontend.cljs
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
(ns reitit.frontend
|
||||||
|
""
|
||||||
|
(:require [reitit.core :as reitit]
|
||||||
|
[clojure.string :as str]
|
||||||
|
[clojure.set :as set]
|
||||||
|
[reitit.coercion :as coercion]
|
||||||
|
[goog.events :as e]
|
||||||
|
[goog.dom :as dom])
|
||||||
|
(:import goog.Uri))
|
||||||
|
|
||||||
|
(defn query-params
|
||||||
|
"Given goog.Uri, read query parameters into Clojure map."
|
||||||
|
[^goog.Uri uri]
|
||||||
|
(let [q (.getQueryData uri)]
|
||||||
|
(->> q
|
||||||
|
(.getKeys)
|
||||||
|
(map (juxt keyword #(.get q %)))
|
||||||
|
(into {}))))
|
||||||
|
|
||||||
|
(defn match-by-path
|
||||||
|
"Given routing tree and current path, return match with possibly
|
||||||
|
coerced parameters. Return nil if no match found."
|
||||||
|
[router path]
|
||||||
|
(let [uri (.parse Uri path)]
|
||||||
|
(if-let [match (reitit/match-by-path router (.getPath uri))]
|
||||||
|
(let [q (query-params uri)
|
||||||
|
;; Return uncoerced values if coercion is not enabled - so
|
||||||
|
;; that tha parameters are always accessible from same property.
|
||||||
|
;; FIXME: coerce! can't be used as it doesn't take query-params
|
||||||
|
parameters (if (:result match)
|
||||||
|
(coercion/coerce-request (:result match) {:query-params q
|
||||||
|
:path-params (:path-params match)})
|
||||||
|
{:query q
|
||||||
|
:path (:path-params match)})]
|
||||||
|
(assoc match :parameters parameters)))))
|
||||||
|
|
||||||
|
(defn match-by-name
|
||||||
|
([router name]
|
||||||
|
(match-by-name router name {}))
|
||||||
|
([router name path-params]
|
||||||
|
(reitit/match-by-name router name path-params)))
|
||||||
|
|
||||||
|
(defn match-by-name!
|
||||||
|
"Logs problems using console.warn"
|
||||||
|
([router name]
|
||||||
|
(match-by-name! router name {}))
|
||||||
|
([router name path-params]
|
||||||
|
(if-let [match (match-by-name router name path-params)]
|
||||||
|
(if (reitit/partial-match? match)
|
||||||
|
(if (every? #(contains? path-params %) (:required match))
|
||||||
|
match
|
||||||
|
(let [defined (-> path-params keys set)
|
||||||
|
missing (set/difference (:required match) defined)]
|
||||||
|
(js/console.warn
|
||||||
|
"missing path-params for route" name
|
||||||
|
{:template (:template match)
|
||||||
|
:missing missing
|
||||||
|
:path-params path-params
|
||||||
|
:required (:required match)})
|
||||||
|
nil))
|
||||||
|
match)
|
||||||
|
(do (js/console.warn "missing route" name)
|
||||||
|
nil))))
|
||||||
40
modules/reitit-frontend/src/reitit/frontend/controllers.cljs
Normal file
40
modules/reitit-frontend/src/reitit/frontend/controllers.cljs
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
(ns reitit.frontend.controllers)
|
||||||
|
|
||||||
|
(defn- pad-same-length [a b]
|
||||||
|
(concat a (take (- (count b) (count a)) (repeat nil))))
|
||||||
|
|
||||||
|
(defn get-params
|
||||||
|
"Get controller parameters given match. If controller provides :params
|
||||||
|
function that will be called with the match. Default is nil."
|
||||||
|
[controller match]
|
||||||
|
(if-let [f (:params controller)]
|
||||||
|
(f match)))
|
||||||
|
|
||||||
|
(defn apply-controller
|
||||||
|
"Run side-effects (:start or :stop) for controller.
|
||||||
|
The side-effect function is called with controller params."
|
||||||
|
[controller method]
|
||||||
|
(when-let [f (get controller method)]
|
||||||
|
(f (::params controller))))
|
||||||
|
|
||||||
|
(defn apply-controllers
|
||||||
|
"Applies changes between current controllers and
|
||||||
|
those previously enabled. Resets controllers whose
|
||||||
|
parameters have changed."
|
||||||
|
[old-controllers new-match]
|
||||||
|
(let [new-controllers (mapv (fn [controller]
|
||||||
|
(assoc controller ::params (get-params controller new-match)))
|
||||||
|
(:controllers (:data new-match)))
|
||||||
|
changed-controllers (->> (map (fn [old new]
|
||||||
|
;; different controllers, or params changed
|
||||||
|
(if (not= old new)
|
||||||
|
{:old old, :new new}))
|
||||||
|
(pad-same-length old-controllers new-controllers)
|
||||||
|
(pad-same-length new-controllers old-controllers))
|
||||||
|
(keep identity)
|
||||||
|
vec)]
|
||||||
|
(doseq [controller (map :old changed-controllers)]
|
||||||
|
(apply-controller controller :stop))
|
||||||
|
(doseq [controller (map :new changed-controllers)]
|
||||||
|
(apply-controller controller :start))
|
||||||
|
new-controllers))
|
||||||
127
modules/reitit-frontend/src/reitit/frontend/history.cljs
Normal file
127
modules/reitit-frontend/src/reitit/frontend/history.cljs
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
(ns reitit.frontend.history
|
||||||
|
""
|
||||||
|
(:require [reitit.core :as reitit]
|
||||||
|
[clojure.string :as string]
|
||||||
|
[goog.events :as e]
|
||||||
|
[goog.dom :as dom]
|
||||||
|
[reitit.core :as r]
|
||||||
|
[reitit.frontend :as rf])
|
||||||
|
(:import goog.history.Html5History
|
||||||
|
goog.Uri))
|
||||||
|
|
||||||
|
;; Token is for Closure HtmlHistory
|
||||||
|
;; Path is for reitit
|
||||||
|
|
||||||
|
(defn- token->path [history token]
|
||||||
|
(if (.-useFragment_ history)
|
||||||
|
;; If no fragment at all, default to "/"
|
||||||
|
;; If fragment is present, the token already is prefixed with "/"
|
||||||
|
(if (= "" token)
|
||||||
|
(.getPathPrefix history)
|
||||||
|
token)
|
||||||
|
(str (.getPathPrefix history) token)))
|
||||||
|
|
||||||
|
(defn- path->token [history path]
|
||||||
|
(subs path (if (.-useFragment_ history)
|
||||||
|
1
|
||||||
|
(count (.getPathPrefix history)))))
|
||||||
|
|
||||||
|
(defn- token->href [history token]
|
||||||
|
(if token
|
||||||
|
(str (if (.-useFragment_ history)
|
||||||
|
(str "#"))
|
||||||
|
(.getPathPrefix history)
|
||||||
|
token)))
|
||||||
|
|
||||||
|
(def ^:private current-domain (if (exists? js/location)
|
||||||
|
(.getDomain (.parse Uri js/location))))
|
||||||
|
|
||||||
|
(defn ignore-anchor-click
|
||||||
|
"Ignore click events from a elements, if the href points to URL that is part
|
||||||
|
of the routing tree."
|
||||||
|
[router history e]
|
||||||
|
;; Returns the next matching anchestor of event target
|
||||||
|
(when-let [el (.closest (.-target e) "a")]
|
||||||
|
(let [uri (.parse Uri (.-href el))]
|
||||||
|
(when (and (or (and (not (.hasScheme uri)) (not (.hasDomain uri)))
|
||||||
|
(= current-domain (.getDomain uri)))
|
||||||
|
(not (.-altKey e))
|
||||||
|
(not (.-ctrlKey e))
|
||||||
|
(not (.-metaKey e))
|
||||||
|
(not (.-shiftKey e))
|
||||||
|
(not (contains? #{"_blank" "self"} (.getAttribute el "target")))
|
||||||
|
;; Left button
|
||||||
|
(= 0 (.-button e))
|
||||||
|
(reitit/match-by-path router (.getPath uri)))
|
||||||
|
(.preventDefault e)
|
||||||
|
(.replaceToken history (path->token history (.getPath uri)))))))
|
||||||
|
|
||||||
|
(defn start!
|
||||||
|
"This registers event listeners on either haschange or HTML5 history.
|
||||||
|
When using with development workflow like Figwheel, rememeber to
|
||||||
|
remove listeners using stop! call before calling start! again.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- router The reitit routing tree.
|
||||||
|
- on-navigate Function to be called when route changes.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
- :use-fragment (default true) If true, onhashchange and location hash are used to store the token.
|
||||||
|
- :path-prefix (default \"/\") If :use-fragment is false, this is prepended to all tokens, and is
|
||||||
|
removed from start of the token before matching the route."
|
||||||
|
[router
|
||||||
|
on-navigate
|
||||||
|
{:keys [path-prefix use-fragment]
|
||||||
|
:or {path-prefix "/"
|
||||||
|
use-fragment true}}]
|
||||||
|
(let [history
|
||||||
|
(doto (Html5History.)
|
||||||
|
(.setEnabled true)
|
||||||
|
(.setPathPrefix path-prefix)
|
||||||
|
(.setUseFragment use-fragment))
|
||||||
|
|
||||||
|
event-key
|
||||||
|
(e/listen history goog.history.EventType.NAVIGATE
|
||||||
|
(fn [e]
|
||||||
|
(on-navigate (rf/match-by-path router (token->path history (.getToken history))))))
|
||||||
|
|
||||||
|
click-listen-key
|
||||||
|
(if-not use-fragment
|
||||||
|
(e/listen js/document e/EventType.CLICK
|
||||||
|
(partial ignore-anchor-click router history)))]
|
||||||
|
|
||||||
|
;; Trigger navigate event for current route
|
||||||
|
(on-navigate (rf/match-by-path router (token->path history (.getToken history))))
|
||||||
|
|
||||||
|
{:router router
|
||||||
|
:history history
|
||||||
|
:close-fn (fn []
|
||||||
|
(e/unlistenByKey event-key)
|
||||||
|
(e/unlistenByKey click-listen-key)
|
||||||
|
(.dispose history))}))
|
||||||
|
|
||||||
|
(defn stop! [{:keys [close-fn]}]
|
||||||
|
(if close-fn
|
||||||
|
(close-fn)))
|
||||||
|
|
||||||
|
(defn- match->token [history match k params query]
|
||||||
|
(some->> (r/match->path match query)
|
||||||
|
(path->token history)))
|
||||||
|
|
||||||
|
(defn href
|
||||||
|
([state k]
|
||||||
|
(href state k nil))
|
||||||
|
([state k params]
|
||||||
|
(href state k params nil))
|
||||||
|
([{:keys [router history]} k params query]
|
||||||
|
(let [match (rf/match-by-name! router k params)
|
||||||
|
token (match->token history match k params query)]
|
||||||
|
(token->href history token))))
|
||||||
|
|
||||||
|
(defn replace-token
|
||||||
|
([state k params]
|
||||||
|
(replace-token state k params nil))
|
||||||
|
([{:keys [router history]} k params query]
|
||||||
|
(let [match (rf/match-by-name! router k params)
|
||||||
|
token (match->token history match k params query)]
|
||||||
|
(.replaceToken history token))))
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
(defproject metosin/reitit-ring "0.1.3"
|
(defproject metosin/reitit-ring "0.1.4-SNAPSHOT"
|
||||||
:description "Reitit: Ring routing"
|
:description "Reitit: Ring routing"
|
||||||
:url "https://github.com/metosin/reitit"
|
:url "https://github.com/metosin/reitit"
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
(defproject metosin/reitit-schema "0.1.3"
|
(defproject metosin/reitit-schema "0.1.4-SNAPSHOT"
|
||||||
:description "Reitit: Plumatic Schema coercion"
|
:description "Reitit: Plumatic Schema coercion"
|
||||||
:url "https://github.com/metosin/reitit"
|
:url "https://github.com/metosin/reitit"
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
(defproject metosin/reitit-spec "0.1.3"
|
(defproject metosin/reitit-spec "0.1.4-SNAPSHOT"
|
||||||
:description "Reitit: clojure.spec coercion"
|
:description "Reitit: clojure.spec coercion"
|
||||||
:url "https://github.com/metosin/reitit"
|
:url "https://github.com/metosin/reitit"
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
(defproject metosin/reitit-swagger-ui "0.1.3"
|
(defproject metosin/reitit-swagger-ui "0.1.4-SNAPSHOT"
|
||||||
:description "Reitit: Swagger-ui support"
|
:description "Reitit: Swagger-ui support"
|
||||||
:url "https://github.com/metosin/reitit"
|
:url "https://github.com/metosin/reitit"
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
(defproject metosin/reitit-swagger "0.1.3"
|
(defproject metosin/reitit-swagger "0.1.4-SNAPSHOT"
|
||||||
:description "Reitit: Swagger-support"
|
:description "Reitit: Swagger-support"
|
||||||
:url "https://github.com/metosin/reitit"
|
:url "https://github.com/metosin/reitit"
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
(defproject metosin/reitit "0.1.3"
|
(defproject metosin/reitit "0.1.4-SNAPSHOT"
|
||||||
:description "Snappy data-driven router for Clojure(Script)"
|
:description "Snappy data-driven router for Clojure(Script)"
|
||||||
:url "https://github.com/metosin/reitit"
|
:url "https://github.com/metosin/reitit"
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
|
|
@ -11,4 +11,5 @@
|
||||||
[metosin/reitit-spec]
|
[metosin/reitit-spec]
|
||||||
[metosin/reitit-schema]
|
[metosin/reitit-schema]
|
||||||
[metosin/reitit-swagger]
|
[metosin/reitit-swagger]
|
||||||
[metosin/reitit-swagger-ui]])
|
[metosin/reitit-swagger-ui]
|
||||||
|
[metosin/reitit-frontend]])
|
||||||
|
|
|
||||||
27
project.clj
27
project.clj
|
|
@ -1,4 +1,4 @@
|
||||||
(defproject metosin/reitit-parent "0.1.3"
|
(defproject metosin/reitit-parent "0.1.4-SNAPSHOT"
|
||||||
:description "Snappy data-driven router for Clojure(Script)"
|
:description "Snappy data-driven router for Clojure(Script)"
|
||||||
:url "https://github.com/metosin/reitit"
|
:url "https://github.com/metosin/reitit"
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
|
|
@ -9,13 +9,14 @@
|
||||||
:source-uri "https://github.com/metosin/reitit/{version}/{filepath}#L{line}"
|
:source-uri "https://github.com/metosin/reitit/{version}/{filepath}#L{line}"
|
||||||
:metadata {:doc/format :markdown}}
|
:metadata {:doc/format :markdown}}
|
||||||
|
|
||||||
:managed-dependencies [[metosin/reitit "0.1.3"]
|
:managed-dependencies [[metosin/reitit "0.1.4-SNAPSHOT"]
|
||||||
[metosin/reitit-core "0.1.3"]
|
[metosin/reitit-core "0.1.4-SNAPSHOT"]
|
||||||
[metosin/reitit-ring "0.1.3"]
|
[metosin/reitit-ring "0.1.4-SNAPSHOT"]
|
||||||
[metosin/reitit-spec "0.1.3"]
|
[metosin/reitit-spec "0.1.4-SNAPSHOT"]
|
||||||
[metosin/reitit-schema "0.1.3"]
|
[metosin/reitit-schema "0.1.4-SNAPSHOT"]
|
||||||
[metosin/reitit-swagger "0.1.3"]
|
[metosin/reitit-swagger "0.1.4-SNAPSHOT"]
|
||||||
[metosin/reitit-swagger-ui "0.1.3"]
|
[metosin/reitit-swagger-ui "0.1.4-SNAPSHOT"]
|
||||||
|
[metosin/reitit-frontend "0.1.4-SNAPSHOT"]
|
||||||
|
|
||||||
[meta-merge "1.0.0"]
|
[meta-merge "1.0.0"]
|
||||||
[ring/ring-core "1.6.3"]
|
[ring/ring-core "1.6.3"]
|
||||||
|
|
@ -40,10 +41,11 @@
|
||||||
"modules/reitit-spec/src"
|
"modules/reitit-spec/src"
|
||||||
"modules/reitit-schema/src"
|
"modules/reitit-schema/src"
|
||||||
"modules/reitit-swagger/src"
|
"modules/reitit-swagger/src"
|
||||||
"modules/reitit-swagger-ui/src"]
|
"modules/reitit-swagger-ui/src"
|
||||||
|
"modules/reitit-frontend/src"]
|
||||||
|
|
||||||
:dependencies [[org.clojure/clojure "1.9.0"]
|
:dependencies [[org.clojure/clojure "1.9.0"]
|
||||||
[org.clojure/clojurescript "1.10.238"]
|
[org.clojure/clojurescript "1.10.339"]
|
||||||
|
|
||||||
;; modules dependencies
|
;; modules dependencies
|
||||||
[metosin/reitit]
|
[metosin/reitit]
|
||||||
|
|
@ -60,7 +62,10 @@
|
||||||
[criterium "0.4.4"]
|
[criterium "0.4.4"]
|
||||||
[org.clojure/test.check "0.9.0"]
|
[org.clojure/test.check "0.9.0"]
|
||||||
[org.clojure/tools.namespace "0.2.11"]
|
[org.clojure/tools.namespace "0.2.11"]
|
||||||
[com.gfredericks/test.chuck "0.2.9"]]}
|
[com.gfredericks/test.chuck "0.2.9"]
|
||||||
|
|
||||||
|
;; https://github.com/bensu/doo/issues/180
|
||||||
|
[fipp "0.6.12"]]}
|
||||||
:perf {:jvm-opts ^:replace ["-server"
|
:perf {:jvm-opts ^:replace ["-server"
|
||||||
"-Xmx4096m"
|
"-Xmx4096m"
|
||||||
"-Dclojure.compiler.direct-linking=true"]
|
"-Dclojure.compiler.direct-linking=true"]
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,6 @@
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Modules
|
# Modules
|
||||||
for ext in reitit-core reitit-ring reitit-spec reitit-schema reitit-swagger reitit-swagger-ui reitit; do
|
for ext in reitit-core reitit-ring reitit-spec reitit-schema reitit-swagger reitit-swagger-ui reitit-frontend reitit; do
|
||||||
cd modules/$ext; lein "$@"; cd ../..;
|
cd modules/$ext; lein "$@"; cd ../..;
|
||||||
done
|
done
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,10 @@
|
||||||
reitit.impl-test
|
reitit.impl-test
|
||||||
reitit.middleware-test
|
reitit.middleware-test
|
||||||
reitit.ring-test
|
reitit.ring-test
|
||||||
#_reitit.spec-test))
|
#_reitit.spec-test
|
||||||
|
reitit.frontend.core-test
|
||||||
|
reitit.frontend.history-test
|
||||||
|
reitit.frontend.controllers-test))
|
||||||
|
|
||||||
(enable-console-print!)
|
(enable-console-print!)
|
||||||
|
|
||||||
|
|
@ -14,4 +17,7 @@
|
||||||
'reitit.impl-test
|
'reitit.impl-test
|
||||||
'reitit.middleware-test
|
'reitit.middleware-test
|
||||||
'reitit.ring-test
|
'reitit.ring-test
|
||||||
#_'reitit.spec-test)
|
#_'reitit.spec-test
|
||||||
|
'reitit.frontend.core-test
|
||||||
|
'reitit.frontend.history-test
|
||||||
|
'reitit.frontend.controllers-test)
|
||||||
|
|
|
||||||
68
test/cljs/reitit/frontend/controllers_test.cljs
Normal file
68
test/cljs/reitit/frontend/controllers_test.cljs
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
(ns reitit.frontend.controllers-test
|
||||||
|
(:require [clojure.test :refer [deftest testing is are]]
|
||||||
|
[reitit.frontend.controllers :as rfc]))
|
||||||
|
|
||||||
|
(deftest apply-controller-test
|
||||||
|
(is (= :ok (rfc/apply-controller {:stop (fn [_] :ok)} :stop)))
|
||||||
|
(is (= :ok (rfc/apply-controller {:start (fn [_] :ok)} :start))))
|
||||||
|
|
||||||
|
(deftest apply-controllers-test
|
||||||
|
(let [log (atom [])
|
||||||
|
controller-state (atom [])
|
||||||
|
controller-1 {:start (fn [_] (swap! log conj :start-1))
|
||||||
|
:stop (fn [_] (swap! log conj :stop-1))}
|
||||||
|
controller-2 {:start (fn [_] (swap! log conj :start-2))
|
||||||
|
:stop (fn [_] (swap! log conj :stop-2))}
|
||||||
|
controller-3 {:start (fn [{:keys [foo]}] (swap! log conj [:start-3 foo]))
|
||||||
|
:stop (fn [{:keys [foo]}] (swap! log conj [:stop-3 foo]))
|
||||||
|
:params (fn [match]
|
||||||
|
{:foo (-> match :parameters :path :foo)})}]
|
||||||
|
|
||||||
|
(testing "single controller started"
|
||||||
|
(swap! controller-state rfc/apply-controllers
|
||||||
|
{:data {:controllers [controller-1]}})
|
||||||
|
|
||||||
|
(is (= [:start-1] @log))
|
||||||
|
(is (= [(assoc controller-1 ::rfc/params nil)] @controller-state))
|
||||||
|
(reset! log []))
|
||||||
|
|
||||||
|
(testing "second controller started"
|
||||||
|
(swap! controller-state rfc/apply-controllers
|
||||||
|
{:data {:controllers [controller-1 controller-2]}})
|
||||||
|
|
||||||
|
(is (= [:start-2] @log))
|
||||||
|
(is (= [(assoc controller-1 ::rfc/params nil)
|
||||||
|
(assoc controller-2 ::rfc/params nil)]
|
||||||
|
@controller-state))
|
||||||
|
(reset! log []))
|
||||||
|
|
||||||
|
(testing "second controller replaced"
|
||||||
|
(swap! controller-state rfc/apply-controllers
|
||||||
|
{:data {:controllers [controller-1 controller-3]}
|
||||||
|
:parameters {:path {:foo 5}}})
|
||||||
|
|
||||||
|
(is (= [:stop-2 [:start-3 5]] @log))
|
||||||
|
(is (= [(assoc controller-1 ::rfc/params nil)
|
||||||
|
(assoc controller-3 ::rfc/params {:foo 5})]
|
||||||
|
@controller-state))
|
||||||
|
(reset! log []))
|
||||||
|
|
||||||
|
(testing "controller parameter changed"
|
||||||
|
(swap! controller-state rfc/apply-controllers
|
||||||
|
{:data {:controllers [controller-1 controller-3]}
|
||||||
|
:parameters {:path {:foo 1}}})
|
||||||
|
|
||||||
|
(is (= [[:stop-3 5] [:start-3 1]] @log))
|
||||||
|
(is (= [(assoc controller-1 ::rfc/params nil)
|
||||||
|
(assoc controller-3 ::rfc/params {:foo 1})]
|
||||||
|
@controller-state))
|
||||||
|
(reset! log []))
|
||||||
|
|
||||||
|
(testing "all controllers stopped"
|
||||||
|
(swap! controller-state rfc/apply-controllers
|
||||||
|
{:data {:controllers []}})
|
||||||
|
|
||||||
|
(is (= [:stop-1 [:stop-3 1]] @log))
|
||||||
|
(is (= [] @controller-state))
|
||||||
|
(reset! log []))
|
||||||
|
))
|
||||||
89
test/cljs/reitit/frontend/core_test.cljs
Normal file
89
test/cljs/reitit/frontend/core_test.cljs
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
(ns reitit.frontend.core-test
|
||||||
|
(:require [clojure.test :refer [deftest testing is are]]
|
||||||
|
[reitit.core :as r]
|
||||||
|
[reitit.frontend :as rf]
|
||||||
|
[reitit.coercion :as rc]
|
||||||
|
[schema.core :as s]
|
||||||
|
[reitit.coercion.schema :as rsc]
|
||||||
|
[reitit.frontend.test-utils :refer [capture-console]]))
|
||||||
|
|
||||||
|
(defn m [x]
|
||||||
|
(assoc x :data nil :result nil))
|
||||||
|
|
||||||
|
(deftest match-by-path-test
|
||||||
|
(testing "simple"
|
||||||
|
(let [router (r/router ["/"
|
||||||
|
["" ::frontpage]
|
||||||
|
["foo" ::foo]
|
||||||
|
["bar" ::bar]])]
|
||||||
|
(is (= (r/map->Match
|
||||||
|
{:template "/"
|
||||||
|
:data {:name ::frontpage}
|
||||||
|
:path-params {}
|
||||||
|
:path "/"
|
||||||
|
:parameters {:query {}
|
||||||
|
:path {}}})
|
||||||
|
(rf/match-by-path router "/")))
|
||||||
|
|
||||||
|
(is (= "/"
|
||||||
|
(r/match->path (rf/match-by-name router ::frontpage))))
|
||||||
|
|
||||||
|
(is (= (r/map->Match
|
||||||
|
{:template "/foo"
|
||||||
|
:data {:name ::foo}
|
||||||
|
:path-params {}
|
||||||
|
:path "/foo"
|
||||||
|
:parameters {:query {}
|
||||||
|
:path {}}})
|
||||||
|
(rf/match-by-path router "/foo")))
|
||||||
|
|
||||||
|
(is (= "/foo"
|
||||||
|
(r/match->path (rf/match-by-name router ::foo))))
|
||||||
|
|
||||||
|
(is (= [{:type :warn
|
||||||
|
:message ["missing route" ::asd]}]
|
||||||
|
(:messages
|
||||||
|
(capture-console
|
||||||
|
(fn []
|
||||||
|
(rf/match-by-name! router ::asd))))))))
|
||||||
|
|
||||||
|
(testing "schema coercion"
|
||||||
|
(let [router (r/router ["/"
|
||||||
|
[":id" {:name ::foo
|
||||||
|
:parameters {:path {:id s/Int}
|
||||||
|
:query {(s/optional-key :mode) s/Keyword}}}]]
|
||||||
|
{:compile rc/compile-request-coercers
|
||||||
|
:data {:coercion rsc/coercion}})]
|
||||||
|
(is (= (r/map->Match
|
||||||
|
{:template "/:id"
|
||||||
|
:path-params {:id "5"}
|
||||||
|
:path "/5"
|
||||||
|
:parameters {:query {}
|
||||||
|
:path {:id 5}}})
|
||||||
|
(m (rf/match-by-path router "/5"))))
|
||||||
|
|
||||||
|
(is (= "/5"
|
||||||
|
(r/match->path (rf/match-by-name router ::foo {:id 5}))))
|
||||||
|
|
||||||
|
(is (= (r/map->Match
|
||||||
|
{:template "/:id"
|
||||||
|
:path-params {:id "5"}
|
||||||
|
;; Note: query not included in path
|
||||||
|
:path "/5"
|
||||||
|
:parameters {:path {:id 5}
|
||||||
|
:query {:mode :foo}}})
|
||||||
|
(m (rf/match-by-path router "/5?mode=foo"))))
|
||||||
|
|
||||||
|
(is (= "/5?mode=foo"
|
||||||
|
(r/match->path (rf/match-by-name router ::foo {:id 5}) {:mode :foo})))
|
||||||
|
|
||||||
|
(is (= [{:type :warn
|
||||||
|
:message ["missing path-params for route" ::foo
|
||||||
|
{:template "/:id"
|
||||||
|
:missing #{:id}
|
||||||
|
:required #{:id}
|
||||||
|
:path-params {}}]}]
|
||||||
|
(:messages
|
||||||
|
(capture-console
|
||||||
|
(fn []
|
||||||
|
(rf/match-by-name! router ::foo {})))))))))
|
||||||
59
test/cljs/reitit/frontend/history_test.cljs
Normal file
59
test/cljs/reitit/frontend/history_test.cljs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
(ns reitit.frontend.history-test
|
||||||
|
(:require [clojure.test :refer [deftest testing is are]]
|
||||||
|
[reitit.core :as r]
|
||||||
|
[reitit.frontend.history :as rfh]
|
||||||
|
[reitit.frontend.test-utils :refer [capture-console]]))
|
||||||
|
|
||||||
|
(def browser (exists? js/window))
|
||||||
|
|
||||||
|
(deftest fragment-history-test
|
||||||
|
(when browser
|
||||||
|
(let [router (r/router ["/"
|
||||||
|
["" ::frontpage]
|
||||||
|
["foo" ::foo]
|
||||||
|
["bar/:id" ::bar]])
|
||||||
|
history (rfh/start! router
|
||||||
|
(fn [_])
|
||||||
|
{:use-fragment true
|
||||||
|
:path-prefix "/"})]
|
||||||
|
|
||||||
|
(testing "creating urls"
|
||||||
|
(is (= "#/foo"
|
||||||
|
(rfh/href history ::foo)))
|
||||||
|
(is (= "#/bar/5"
|
||||||
|
(rfh/href history ::bar {:id 5})))
|
||||||
|
(is (= "#/bar/5?q=x"
|
||||||
|
(rfh/href history ::bar {:id 5} {:q "x"})))
|
||||||
|
(let [{:keys [value messages]} (capture-console
|
||||||
|
(fn []
|
||||||
|
(rfh/href history ::asd)))]
|
||||||
|
(is (= nil value))
|
||||||
|
(is (= [{:type :warn
|
||||||
|
:message ["missing route" ::asd]}]
|
||||||
|
messages)))))))
|
||||||
|
|
||||||
|
(deftest html5-history-test
|
||||||
|
(when browser
|
||||||
|
(let [router (r/router ["/"
|
||||||
|
["" ::frontpage]
|
||||||
|
["foo" ::foo]
|
||||||
|
["bar/:id" ::bar]])
|
||||||
|
history (rfh/start! router
|
||||||
|
(fn [_])
|
||||||
|
{:use-fragment false
|
||||||
|
:path-prefix "/"})]
|
||||||
|
|
||||||
|
(testing "creating urls"
|
||||||
|
(is (= "/foo"
|
||||||
|
(rfh/href history ::foo)))
|
||||||
|
(is (= "/bar/5"
|
||||||
|
(rfh/href history ::bar {:id 5})))
|
||||||
|
(is (= "/bar/5?q=x"
|
||||||
|
(rfh/href history ::bar {:id 5} {:q "x"})))
|
||||||
|
(let [{:keys [value messages]} (capture-console
|
||||||
|
(fn []
|
||||||
|
(rfh/href history ::asd)))]
|
||||||
|
(is (= nil value))
|
||||||
|
(is (= [{:type :warn
|
||||||
|
:message ["missing route" ::asd]}]
|
||||||
|
messages)))))))
|
||||||
15
test/cljs/reitit/frontend/test_utils.cljs
Normal file
15
test/cljs/reitit/frontend/test_utils.cljs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
(ns reitit.frontend.test-utils)
|
||||||
|
|
||||||
|
(defn capture-console [f]
|
||||||
|
(let [messages (atom [])
|
||||||
|
original-console-warn js/console.warn
|
||||||
|
log (fn [t & message]
|
||||||
|
(swap! messages conj {:type t
|
||||||
|
:message message}))
|
||||||
|
value (try
|
||||||
|
(set! js/console.warn (partial log :warn))
|
||||||
|
(f)
|
||||||
|
(finally
|
||||||
|
(set! js/console.warn original-console-warn)))]
|
||||||
|
{:value value
|
||||||
|
:messages @messages}))
|
||||||
Loading…
Reference in a new issue