├── .gitattributes
├── src
├── index.js
├── static
│ └── purs-logo.png
├── index.html
├── Components
│ ├── App.purs
│ ├── Header.purs
│ ├── UserProfile.purs
│ ├── TextBox.purs
│ └── Form.purs
├── Helpers
│ ├── LoadedData.purs
│ └── ApiCall.purs
├── Main.purs
└── main.css
├── static
└── demo.gif
├── .gitignore
├── psc-package.json
├── .travis.yml
├── package.json
├── test
└── Main.purs
└── README.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | packages.nix linguist-vendored
2 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | require("../output/Main/index.js").main();
2 |
--------------------------------------------------------------------------------
/static/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danieljharvey/another-react-basic-starter/HEAD/static/demo.gif
--------------------------------------------------------------------------------
/src/static/purs-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danieljharvey/another-react-basic-starter/HEAD/src/static/purs-logo.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /bower_components/
2 | /node_modules/
3 | /.pulp-cache/
4 | /output/
5 | /build/
6 | /generated-docs/
7 | /.psc-package/
8 | /.psc*
9 | /.purs*
10 | /.psa*
11 | .cache
12 | /dist/
13 |
--------------------------------------------------------------------------------
/psc-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "spacchetti-react-basic-starter",
3 | "set": "psc-0.12.1-20181218",
4 | "source": "https://github.com/purescript/package-sets.git",
5 | "depends": [
6 | "react-basic", "affjax", "simple-json", "test-unit"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: required
2 | dist: trusty
3 | language: node
4 |
5 | before_script:
6 | # install v6.x of nodejs
7 | - nvm install 10.11
8 | #install yarn globally
9 | - npm install -g yarn
10 |
11 | script:
12 | - yarn install
13 | - yarn purs:install
14 | - yarn purs:build
15 | - yarn test
16 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | React Basic Starter
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/Components/App.purs:
--------------------------------------------------------------------------------
1 | module Components.App where
2 |
3 | import Prelude
4 |
5 | import Components.Form (form)
6 | import Components.Header (header)
7 | import React.Basic (Component, JSX, createComponent, makeStateless)
8 | import React.Basic.DOM as R
9 |
10 | component :: Component Unit
11 | component = createComponent "App"
12 |
13 | app :: JSX
14 | app = unit # makeStateless component \_ ->
15 | R.div_
16 | [ header { title: "Hello from React Basic" }
17 | , form { }
18 | ]
19 |
--------------------------------------------------------------------------------
/src/Helpers/LoadedData.purs:
--------------------------------------------------------------------------------
1 | module LoadedData where
2 |
3 | import Data.Maybe
4 | import Data.Eq (class Eq)
5 |
6 | {-
7 | here is a nice example of the Purescript type system allowing us to describe
8 | the state of some data we are loading from a server in a much nicer and more
9 | descriptive manner than just Maybe a or a loading flag
10 | -}
11 |
12 | data FetchData a = NotLoaded | Loading | Ready a | LoadError | DecodeError
13 |
14 | derive instance eqFetchData :: Eq a => Eq (FetchData a)
15 |
16 | fromDecoding :: forall a. Maybe a -> FetchData a
17 | fromDecoding (Just a) = Ready a
18 | fromDecoding _ = DecodeError
19 |
--------------------------------------------------------------------------------
/src/Main.purs:
--------------------------------------------------------------------------------
1 | module Main where
2 |
3 | import Prelude
4 |
5 | import Components.App (app)
6 | import Data.Maybe (Maybe(..))
7 | import Effect (Effect)
8 | import Effect.Exception (throw)
9 | import React.Basic.DOM (render)
10 | import Web.DOM.NonElementParentNode (getElementById)
11 | import Web.HTML (window)
12 | import Web.HTML.HTMLDocument (toNonElementParentNode)
13 | import Web.HTML.Window (document)
14 |
15 | main :: Effect Unit
16 | main = do
17 | root <- getElementById "root" =<< (map toNonElementParentNode $ document =<< window)
18 | case root of
19 | Nothing -> throw "Root element not found."
20 | Just r -> render app r
21 |
--------------------------------------------------------------------------------
/src/Components/Header.purs:
--------------------------------------------------------------------------------
1 | module Components.Header where
2 |
3 | import Prelude
4 |
5 | import React.Basic (Component, JSX, StateUpdate(..), createComponent, make)
6 | import React.Basic.DOM as R
7 |
8 | type HeaderProps =
9 | { title :: String
10 | }
11 |
12 | component :: Component HeaderProps
13 | component = createComponent "Header"
14 |
15 | header :: HeaderProps -> JSX
16 | header = make component { initialState, update, render }
17 | where
18 | initialState = unit
19 |
20 | update self = case _ of
21 | _ -> Update self.state
22 |
23 | render self =
24 | R.div { className: "header"
25 | , children: [ R.div { className: "purs-logo" }
26 | , R.h1_ [ R. text self.props.title ]
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-basic-starter",
3 | "version": "1.0.0",
4 | "description": "An example react-basic project",
5 | "repository": "lumihq/react-basic-starter",
6 | "dependencies": {
7 | "react": "16.6.1",
8 | "react-dom": "16.6.1"
9 | },
10 | "devDependencies": {
11 | "parcel-bundler": "^1.10.3",
12 | "concurrently": "^4.1.0",
13 | "psc-package": "3.0.1",
14 | "pscid": "2.4.0",
15 | "pulp": "12.3.0",
16 | "purescript": "0.12.1"
17 | },
18 | "scripts": {
19 | "start:parcel": "parcel ./src/index.html",
20 | "start:build": "pulp -w build",
21 | "start": "concurrently \"yarn start:parcel\" \"yarn start:build\"",
22 | "purs:install": "psc-package install",
23 | "purs:build": "psc-package build",
24 | "build": "parcel build ./src/index.html",
25 | "pscid": "pscid src/Main.purs",
26 | "test": "pulp test"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Helpers/ApiCall.purs:
--------------------------------------------------------------------------------
1 | module ApiCall where
2 |
3 | import Prelude (($), (<>))
4 | import Data.Maybe (Maybe)
5 | import Data.Either (hush)
6 |
7 | import Simple.JSON (readJSON)
8 |
9 | type GithubUser = {
10 | login :: String,
11 | id :: Int,
12 | node_id :: String,
13 | avatar_url :: String,
14 | gravatar_id :: String,
15 | url :: String,
16 | html_url :: String,
17 | followers_url :: String,
18 | following_url :: String,
19 | gists_url :: String,
20 | starred_url :: String,
21 | subscriptions_url :: String,
22 | organizations_url :: String,
23 | repos_url :: String,
24 | events_url :: String,
25 | received_events_url :: String,
26 | type :: String,
27 | site_admin :: Boolean,
28 | name :: String,
29 | company :: Maybe String,
30 | blog :: String,
31 | location :: String,
32 | email :: Maybe String,
33 | hireable :: Maybe Boolean,
34 | bio :: Maybe String,
35 | public_repos :: Int,
36 | public_gists :: Int,
37 | followers :: Int,
38 | following :: Int,
39 | created_at :: String,
40 | updated_at :: String
41 | }
42 |
43 | type Username = String
44 | type Url = String
45 |
46 | createUrl :: Username -> Url
47 | createUrl username = "https://api.github.com/users/" <> username
48 |
49 | decodeUser :: String -> Maybe GithubUser
50 | decodeUser s = hush $ readJSON s
51 |
--------------------------------------------------------------------------------
/src/Components/UserProfile.purs:
--------------------------------------------------------------------------------
1 | module UserProfile where
2 |
3 | import Prelude (($))
4 | import Data.Maybe (fromMaybe)
5 | import ApiCall (GithubUser)
6 | import React.Basic (JSX)
7 | import React.Basic.DOM (css, CSS)
8 | import React.Basic.DOM as R
9 |
10 | styledText :: CSS -> String -> JSX
11 | styledText styles s = R.span { style: styles, children: [ R.text s ] }
12 |
13 | regularText :: String -> JSX
14 | regularText = styledText $ css { }
15 |
16 | boldText :: String -> JSX
17 | boldText = styledText $ css { "font-weight": 800, color: "lightgrey" }
18 |
19 | avatar :: String -> JSX
20 | avatar s = R.img { style: css { width: "200px", height: "200px" }, src: s }
21 |
22 | link :: String -> JSX
23 | link s = R.a { href: s, children: [ R.text s ] }
24 |
25 | row :: Array JSX -> JSX
26 | row as = R.div { style: css { padding: "10px", width: "100%" }, children: as }
27 |
28 | renderUserProfile :: GithubUser -> JSX
29 | renderUserProfile user =
30 | R.div { className: "userProfile"
31 | , children: [ row [ avatar user.avatar_url ]
32 | , row [ boldText "Username: ", regularText user.login ]
33 | , row [ boldText "Name: ", regularText user.name ]
34 | , row [ boldText "Bio: ", regularText $ fromMaybe "None found" user.bio ]
35 | , row [ boldText "Blog: ", link user.blog ]
36 | ]
37 | }
38 |
--------------------------------------------------------------------------------
/src/Components/TextBox.purs:
--------------------------------------------------------------------------------
1 | module Components.Textbox where
2 |
3 |
4 | import React.Basic (Component, JSX, StateUpdate(..), capture_, createComponent, make)
5 | import React.Basic.DOM as R
6 | import React.Basic.Events (EventHandler)
7 |
8 | type TextboxProps =
9 | { label :: String
10 | , placeholder :: String
11 | , onChange :: EventHandler
12 | }
13 |
14 | type TextboxState =
15 | { hasBlurred :: Boolean
16 | , hasChanged :: Boolean
17 | , hasFocused :: Boolean
18 | }
19 |
20 | data Action =
21 | OnBlur | OnFocus
22 |
23 | component :: Component TextboxProps
24 | component = createComponent "Textbox"
25 |
26 | calcStyleClass :: TextboxState -> String
27 | calcStyleClass state = "great"
28 |
29 | textbox :: TextboxProps -> JSX
30 | textbox = make component { initialState, update, render }
31 | where
32 | initialState = { hasBlurred: false, hasChanged: false, hasFocused: false }
33 |
34 | update self = case _ of
35 | OnBlur -> Update self.state { hasBlurred = true }
36 | OnFocus -> Update self.state { hasFocused = true }
37 |
38 | render self = R.div { className: calcStyleClass self.state
39 | , children: [ R.label_ [ R.text self.props.label ]
40 | , R.input { type: "text"
41 | , onChange: self.props.onChange
42 | , onBlur: capture_ self OnBlur
43 | , onFocus: capture_ self OnFocus
44 | , placeholder: self.props.placeholder
45 | }
46 | ]
47 | }
48 |
--------------------------------------------------------------------------------
/src/main.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: rgb(50, 50, 50);
3 | margin: 0px;
4 | padding: 0px;
5 | font-family: Helvetica, Arial, sans-serif;
6 | color: white;
7 | }
8 |
9 | button {
10 | margin: 10px;
11 | padding: 20px;
12 | font-size: 20px;
13 | }
14 |
15 | button.on {
16 | color: green;
17 | }
18 |
19 | button.off {
20 | color: red;
21 | }
22 |
23 | .header {
24 | flex: 1;
25 | flex-direction: column;
26 | background-color: rgb(80,80,80);
27 | padding-bottom: 30px;
28 | text-align: center;
29 | display: flex;
30 | align-items: center;
31 | }
32 |
33 | .header h1 {
34 | display:flex;
35 | padding: 0px;
36 | margin: 0px;
37 | }
38 |
39 | .form {
40 | padding: 30px;
41 | text-align: center;
42 | }
43 |
44 | .form input {
45 | padding: 10px;
46 | margin: 10px;
47 | min-width: 60%;
48 | }
49 |
50 | a {
51 | color: rgb(192, 160, 160);
52 | }
53 |
54 | .purs-logo {
55 | display:flex;
56 | background: url("./static/purs-logo.png");
57 | background-size: contain;
58 | background-repeat: no-repeat;
59 | background-position: 50% 50%;
60 | mix-blend-mode: lighten;
61 | min-width: 200px;
62 | min-height: 200px;
63 |
64 | -webkit-animation: PURS-LOGO-PULSE 5s infinite; /* Safari 4+ */
65 | -moz-animation: PURS-LOGO-PULSE 5s infinite; /* Fx 5+ */
66 | -o-animation: PURS-LOGO-PULSE 5s infinite; /* Opera 12+ */
67 | animation: PURS-LOGO-PULSE 5s infinite; /* IE 10+, Fx 29+ */
68 | }
69 |
70 |
71 | @-webkit-keyframes PURS-LOGO-PULSE {
72 | 0% { opacity: 0.5; transform: rotate(0deg); }
73 | 50% { opacity: 1; ; transform: rotate(180deg); }
74 | 100% { opacity: 0.5; transform: rotate(360deg); }
75 | }
76 | @-moz-keyframes PURS-LOGO-PULSE {
77 | 0% { opacity: 0.5; transform: rotate(0deg); }
78 | 50% { opacity: 1; ; transform: rotate(180deg); }
79 | 100% { opacity: 0.5; transform: rotate(360deg); }
80 | }
81 | @-o-keyframes PURS-LOGO-PULSE {
82 | 0% { opacity: 0.5; transform: rotate(0deg); }
83 | 50% { opacity: 1; ; transform: rotate(180deg); }
84 | 100% { opacity: 0.5; transform: rotate(360deg); }
85 | }
86 | @keyframes PURS-LOGO-PULSE {
87 | 0% { opacity: 0.5; transform: rotate(0deg); }
88 | 50% { opacity: 1; ; transform: rotate(180deg); }
89 | 100% { opacity: 0.5; transform: rotate(360deg); }
90 | }
91 |
--------------------------------------------------------------------------------
/src/Components/Form.purs:
--------------------------------------------------------------------------------
1 | module Components.Form where
2 |
3 | import Prelude
4 |
5 | import React.Basic (Component, JSX, StateUpdate(..), capture, capture_, createComponent, make, send)
6 | import React.Basic.DOM as R
7 | import React.Basic.DOM.Events (preventDefault, targetValue)
8 | import Data.Maybe (Maybe(..), fromMaybe)
9 | import Data.Either (hush)
10 | import Data.Argonaut.Core as J
11 | import Affjax as AX
12 | import Affjax.ResponseFormat as ResponseFormat
13 | import Effect.Aff (launchAff_)
14 | import ApiCall (createUrl, decodeUser, GithubUser)
15 | import Effect.Class (liftEffect)
16 | import Components.Textbox (textbox)
17 | import UserProfile (renderUserProfile)
18 | import LoadedData (FetchData(..), fromDecoding)
19 |
20 | type FormState =
21 | { text :: String
22 | , githubUser :: FetchData GithubUser
23 | }
24 |
25 | type FormProps =
26 | {
27 | }
28 |
29 | data FormAction = OnChange (Maybe String) | Request | OnReceive (Maybe String)
30 |
31 | component :: Component FormProps
32 | component = createComponent "Form"
33 |
34 | showGithub :: FetchData GithubUser -> JSX
35 | showGithub NotLoaded = R.p_ [ R.text "-" ]
36 | showGithub Loading = R.p_ [ R.text "loading..." ]
37 | showGithub LoadError = R.p_ [ R.text "No user found" ]
38 | showGithub DecodeError = R.p_ [ R.text "Error decoding payload" ]
39 | showGithub (Ready user) = renderUserProfile user
40 |
41 | form :: FormProps -> JSX
42 | form = make component { initialState, update, render }
43 | where
44 | initialState = { text: "", githubUser: NotLoaded }
45 |
46 | update self = case _ of
47 | Request -> UpdateAndSideEffects self.state { githubUser = Loading }
48 | (\s -> launchAff_ $ do
49 | res1 <- (AX.get ResponseFormat.json $ createUrl self.state.text)
50 | let maybeStr = hush $ map J.stringify res1.body
51 | liftEffect $ send self $ OnReceive maybeStr
52 | pure unit
53 | )
54 | OnChange str -> Update self.state { text = fromMaybe "" str }
55 | OnReceive (Just a) -> Update self.state { githubUser = fromDecoding $ decodeUser a }
56 | OnReceive Nothing -> Update self.state { githubUser = LoadError }
57 |
58 | render self =
59 | R.div { className: "form"
60 | , children: [ textbox { label: "Github username"
61 | , placeholder: "internetuser"
62 | , onChange: capture self (preventDefault >>> targetValue) OnChange
63 | }
64 | , showGithub self.state.githubUser
65 | , R.button
66 | { onClick: capture_ self Request, children: [ R.text "fetch" ] }
67 | ]
68 | }
69 |
--------------------------------------------------------------------------------
/test/Main.purs:
--------------------------------------------------------------------------------
1 | module Test.Main where
2 |
3 | import Prelude
4 | import Data.Maybe (Maybe(..))
5 | import Effect (Effect)
6 | import Test.Unit (suite, test)
7 | import Test.Unit.Main (runTest)
8 | import Test.Unit.Assert as Assert
9 | import ApiCall (GithubUser, decodeUser)
10 |
11 | main :: Effect Unit
12 | main = runTest do
13 | suite "Parsing github user" do
14 | test "Parses it all" do
15 | Assert.equal (Just expected) (decodeUser testJson)
16 |
17 |
18 | expected :: GithubUser
19 | expected =
20 | { login : "d"
21 | , id : 440892
22 | , node_id : "MDQ6VXNlcjQ0MDg5Mg=="
23 | , avatar_url : "https://avatars2.githubusercontent.com/u/440892?v=4"
24 | , gravatar_id :""
25 | , url :"https://api.github.com/users/d"
26 | , html_url : "https://github.com/d"
27 | , followers_url: "https://api.github.com/users/d/followers"
28 | , following_url: "https://api.github.com/users/d/following{/other_user}"
29 | , gists_url : "https://api.github.com/users/d/gists{/gist_id}"
30 | , starred_url: "https://api.github.com/users/d/starred{/owner}{/repo}"
31 | , subscriptions_url: "https://api.github.com/users/d/subscriptions"
32 | , organizations_url: "https://api.github.com/users/d/orgs"
33 | , repos_url: "https://api.github.com/users/d/repos"
34 | , events_url: "https://api.github.com/users/d/events{/privacy}"
35 | , received_events_url: "https://api.github.com/users/d/received_events"
36 | , "type": "User"
37 | , site_admin: false
38 | , name: "Jesse Zhang"
39 | , company: Just "Pivotal"
40 | , blog: "cloudfoundry.org"
41 | , location: "San Francisco"
42 | , email: Nothing
43 | , hireable: Nothing
44 | , bio: Nothing
45 | , public_repos: 88
46 | , public_gists: 28
47 | , followers: 81
48 | , following: 8
49 | , created_at: "2010-10-15T14:54:40Z"
50 | , updated_at: "2018-12-11T04:53:04Z"
51 | }
52 |
53 | testJson :: String
54 | testJson = "{\"login\":\"d\",\"id\":440892,\"node_id\":\"MDQ6VXNlcjQ0MDg5Mg==\",\"avatar_url\":\"https://avatars2.githubusercontent.com/u/440892?v=4\",\"gravatar_id\":\"\",\"url\":\"https://api.github.com/users/d\",\"html_url\":\"https://github.com/d\",\"followers_url\":\"https://api.github.com/users/d/followers\",\"following_url\":\"https://api.github.com/users/d/following{/other_user}\",\"gists_url\":\"https://api.github.com/users/d/gists{/gist_id}\",\"starred_url\":\"https://api.github.com/users/d/starred{/owner}{/repo}\",\"subscriptions_url\":\"https://api.github.com/users/d/subscriptions\",\"organizations_url\":\"https://api.github.com/users/d/orgs\",\"repos_url\":\"https://api.github.com/users/d/repos\",\"events_url\":\"https://api.github.com/users/d/events{/privacy}\",\"received_events_url\":\"https://api.github.com/users/d/received_events\",\"type\":\"User\",\"site_admin\":false,\"name\":\"Jesse Zhang\",\"company\":\"Pivotal\",\"blog\":\"cloudfoundry.org\",\"location\":\"San Francisco\",\"email\":null,\"hireable\":null,\"bio\":null,\"public_repos\":88,\"public_gists\":28,\"followers\":81,\"following\":8,\"created_at\":\"2010-10-15T14:54:40Z\",\"updated_at\":\"2018-12-11T04:53:04Z\"}"
55 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Another Purescript React Basic Starter
2 |
3 | [](https://travis-ci.org/danieljharvey/another-react-basic-starter)
4 |
5 | ### What is it?
6 |
7 | 
8 |
9 | ### Aims
10 |
11 | This is a fairly basic starter for Purescript React Basic with (hopefully) enough features to tinker with and demonstrate that React Basic with Purescript is a pretty OK way to make web apps.
12 |
13 | This repository is not an example of idiomatic Purescript development as I would not consider myself experienced enough to really know what that is, however hopefully it should be enough to get an interested beginner on their feet and making/breaking things.
14 |
15 | ### What is Purescript?
16 |
17 | Purescript is a Haskell-esq pure functional language that compiles to Javascript.
18 |
19 | ### What is React Basic?
20 |
21 | React Basic is a set of bindings to React created by [Lumi](https://github.com/lumihq). It contains a number of additional features, most interestingly a reducer-esq way of handling component state ala [ReasonReact](https://reasonml.github.io/reason-react/). For more details see the [repo](https://github.com/lumihq/purescript-react-basic) or the [documentation](https://pursuit.purescript.org/packages/purescript-react-basic/6.2.0/docs/React.Basic).
22 |
23 | ### What else in in here?
24 |
25 | As well as the `react-basic` package it uses `affjax` for fetching content, `simple-json` for decoding json data, and `test-unit` for unit tests. You can bin all this other stuff if you like, it is merely there because it is what I prefer.
26 |
27 | ### Getting Started
28 |
29 | Download the repository:
30 |
31 | ```bash
32 | git clone https://https://github.com/danieljharvey/another-react-basic-starter
33 | ```
34 |
35 | Change to the exciting new folder you have created.
36 |
37 | ```bash
38 | cd another-react-basic-starter
39 | ```
40 |
41 | Install the Javascript dependencies (you can also use `npm` here, your funeral, etc)
42 |
43 | ```bash
44 | yarn install
45 | ```
46 |
47 | This will build all of the Purescript in the project (and more importantly, show you any errors)
48 |
49 | ```bash
50 | yarn purs:build
51 | ```
52 |
53 | This will run the app on a Parcel server, which you can view by navigating to `localhost:1234` in your browser. It also runs `pulp -w build` to make sure your code changes update in the browser.
54 |
55 | ```bash
56 | yarn start
57 | ```
58 |
59 | There are also some basic unit tests you can run with
60 |
61 | ```bash
62 | yarn test
63 | ```
64 |
65 | And once you are ready to share your creation with the world, you can make a static version with:
66 |
67 | ```bash
68 | yarn purs:build
69 | ```
70 |
71 | ### What does all this rubbish do then?
72 |
73 | #### Purescript
74 |
75 | `purescript` is a Haskell-esq language that compiles to (amongst other things) Javascript.
76 |
77 |
78 |
79 | #### Psc-package
80 |
81 | `psc-package` is a package manager for Purescript which uses sets of packages to ensure everything we use it going to work together. Conceptually it is similar to `Stack` used by Haskell.
82 |
83 |
84 |
85 | #### Pulp
86 |
87 | `pulp` is a Purescript build tool that does a whole host of good things (and has a nice GIF of Jarvis Cocker dancing on it's github). It is used here for running the build and the unit tests.
88 |
89 |
90 |
91 | #### Parcel
92 |
93 | `parcel` is a zero-config web app bundler. It is used here to smash everything together and put it in a browser.
94 |
95 |
96 |
97 | #### Test-unit
98 |
99 | `test-unit` is a Purescript test runner that let's us do async tests and other good things.
100 |
101 |
102 |
103 | #### Simple-json
104 |
105 | `simple-json` is a package for converting to and from JSON, without having to do very much work, which I thoroughly approve of.
106 |
107 |
108 |
109 | #### Affjax
110 |
111 | `affjax` is a Purescript library for making AJAX calls.
112 |
113 |
114 |
115 | #### Pscid
116 |
117 | `pscid` is a Purescript file watcher that is pretty handy for checking type errors as you work. I create quite a lot of these so this is pretty essential.
118 |
119 |
120 |
121 | ### History
122 |
123 | This is a fork of [spacchetti-react-basic-starter](https://github.com/justinwoo/spacchetti-react-basic-starter), which is in turn a fork of the original [LumiHQ/React-Basic-Starter](https://github.com/lumihq/react-basic-starter).
124 |
125 | It removes the Nix and Spacchetti stuff (not because that stuff is not great, but I was concerned it may be too many new concepts at once for a beginner) and added a more fully featured sample app to demonstrate that indeed, making (semi) nice things is possible with this setup.
126 |
--------------------------------------------------------------------------------