Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
c6fb555
Refactor to prepare for full release
robertdp Jul 20, 2022
f8454fc
Update CI
robertdp Jul 20, 2022
3d32e6d
Split up reexports
robertdp Jul 20, 2022
005f117
Expose routing DSL commands
robertdp Jul 20, 2022
a516321
Remove unnecessary braces
robertdp Jul 20, 2022
be98328
Update readme
robertdp Jul 20, 2022
aab911c
Export Driver' alias
robertdp Jul 20, 2022
8a1f9a6
Loosen internal exports
robertdp Jul 20, 2022
6d6af59
More explicit naming
robertdp Jul 20, 2022
aa7ee22
Change Driver to a type alias
robertdp Jul 20, 2022
70b35fb
Add back fiber tracking and killing
robertdp Jul 21, 2022
77f7ec1
Update to purs-tidy
robertdp Jul 21, 2022
bb5091d
Strip commends from Dhall files
robertdp Jul 21, 2022
c644c8e
Flesh out readme
robertdp Jul 21, 2022
606c49f
Add note about symmetry
robertdp Jul 21, 2022
ea06c35
Language
robertdp Jul 21, 2022
5bfd1b5
Update routes
robertdp Jul 21, 2022
a88f04c
Mention React
robertdp Jul 21, 2022
ea47f6f
Fix qualified example
robertdp Jul 21, 2022
249ba1a
Update var name
robertdp Jul 21, 2022
ac05db9
Update var name
robertdp Jul 21, 2022
663bef8
Language
robertdp Jul 21, 2022
1147522
Language
robertdp Jul 21, 2022
ede2647
Fix hidden code blocks
robertdp Jul 21, 2022
2efb416
Ix qualified exports
robertdp Jul 21, 2022
a4f6fdd
Switch to mkInterface pattern
robertdp Jul 21, 2022
54caf80
Fix example bug
robertdp Jul 21, 2022
767d3e3
More detailed nav example
robertdp Jul 21, 2022
27f858c
Fix naming
robertdp Jul 21, 2022
7f3c38f
Clearer language
robertdp Jul 21, 2022
632be04
Simplify the example structure
robertdp Jul 21, 2022
0729dbc
Update example
robertdp Jul 21, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ jobs:
steps:
- uses: actions/checkout@v2

- uses: actions/setup-node@v1
- uses: actions/setup-node@v3

- uses: thomashoneyman/setup-purescript@main
with:
purescript: "0.14.0"
spago: "0.19.1"
purescript: "0.15.4"
spago: "0.20.9"

- name: Cache PureScript dependencies
uses: actions/cache@v2
Expand Down
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"purescript.addNpmPath": true,
"purescript.addPscPackageSources": true,
"purescript.addSpagoSources": true,
"purescript.editorMode": true,
"purescript.buildCommand": "spago build -- --json-errors",
"purescript.formatter": "purs-tidy",
"[purescript]": {
"editor.formatOnSave": true
}
Expand Down
168 changes: 143 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,148 @@
# purescript-web-router

A basic pushstate router for React with support for asynchronous routing logic, built using [react-basic-hooks](https://github.com/spicydonuts/purescript-react-basic-hooks). I recommend [routing-duplex](https://github.com/natefaubion/purescript-routing-duplex) for easy parsing and printing.

For a basic example see [here](https://github.com/robertdp/purescript-web-router-example/tree/master/src).

## How to use with Spago

Add `web-router` to your `packages.dhall`:

```dhall
let additions =
{ web-router =
{ dependencies =
[ "aff"
, "effect"
, "freet"
, "indexed-monad"
, "prelude"
, "profunctor-lenses"
, "routing"
]
, repo = "https://github.com/robertdp/purescript-web-router.git"
, version = "v0.3.0"
A router for browsers that supports asynchronous routing logic. Bring your own printing and parsing (check out [routing-duplex](https://github.com/natefaubion/purescript-routing-duplex)).

For a basic React example see [here](https://github.com/robertdp/purescript-web-router-example/tree/master/src).

## How to use

### 1. Install with Spago

`$ spago install web-router` (coming soon)

### 2. Define your routes

```purescript
data Route
= Page Page
| NotFound

data Page
= Home
| ProductList
| ProductView ProductId
| About
| ContactUs

type ProductId = Int
```

### 3. Implement parsing and printing

This example uses [routing-duplex](https://github.com/natefaubion/purescript-routing-duplex).

<details>
<summary>Imports</summary>

```purescript
import Prelude hiding ((/))
import Data.Either (Either)
import Data.Generic.Rep (class Generic)
import Routing.Duplex (RouteDuplex', default, end, int, parse, print, root, segment)
import Routing.Duplex.Generic (noArgs, sum)
import Routing.Duplex.Generic.Syntax ((/))
import Routing.Duplex.Parser (RouteError)
```

</details>

```purescript
derive instance Generic Route _
derive instance Generic Page _

productId :: RouteDuplex' ProductId
productId = int segment

routes :: RouteDuplex' Route
routes =
default NotFound $
sum
{ "Page": pages
, "NotFound": "404" / noArgs
}

pages :: RouteDuplex' Page
pages =
root $ end $
sum
{ "Home": noArgs
, "ProductList": "products" / noArgs
, "ProductView": "products" / productId
, "About": "about" / noArgs
, "ContactUs": "about" / noArgs
}
}

-- | This is route parser we need to pass to the driver.
-- | It can produce any route which allows the parser to return a value of `NotFound` instead of failing.
parseRoute :: forall String -> Either RouteError Route
parseRoute = parse routes

-- | This is the route printer we need to pass to the driver.
-- | It can only print paths to valid pages, which means a path can't be produced for the `NotFound` route.
-- | With this approach routes can be seperated based on whether they should be a navigation target and have a URL.
-- | Note: assymetry is not required, and a symmetrical printer works as well.
printRoute :: Page -> String
printRoute = print pages
```

### Define how your application reacts to navigation and routing events

<details>
<summary>Imports</summary>

```purescript
import Web.Router as Router
```

</details>

```purescript
onNavigation :: Maybe Route -> Route -> Router.RouterM Route Page Router.Routing Router.Resolved Unit
onNavigation previousRoute requestedRoute =
case requestedRoute of
NotFound ->
case previousRoute of
Just (Page page) -> Router.do
liftEffect showBrokenNavigationMessage
Router.redirect page -- redirect back to the previous page and show a message
_ ->
Router.continue -- no previous page, so just show the "not found" page
_ -> Router.do
access <- liftAff fetchUserAccess
if userHasAccess requestedRoute access then
Router.continue -- they have access, so resolve with the requested page
else
Router.override NotFound -- no access, so pretend the page doesn't exist


onEvent :: Router.RoutingEvent Route -> Effect Unit
onEvent newEvent =
case newEvent of
Router.Routing previousRoute requestedRoute ->
showNavigationSpinner
Router.Resolved previousRoute newRoute ->
hideNavigationSpinner
setCurrentRoute newRoute
```

### Connect up the driver and router

<details>
<summary>Imports</summary>

```purescript
import Web.Router as Router
import Web.Router.PushState as PushState
```

</details>

```purescript
mkRouter :: Effect (Router.Router Route Page)
mkRouter = do
driver <- PushState.mkInterface parseRoute printRoute
router <- Router.mkInterface onNavigation onEvent driver
pure router
```

And then install with
`$ spago install web-router`
Both pushstate and hash drivers are included, or a custom driver can be implemented. An example of a custom driver could be one that synchronises some navigation state over sockets, for an experience where one user's behaviour could be broadcast to multiple users to follow along.
Loading