|
| 1 | +# Getting Started |
| 2 | + |
| 3 | +I assume you're here because you want to write a React app using PureScript! Good, good, because that's what this guide is all about. |
| 4 | + |
| 5 | +Before we begin you'll need to install [Node.js](https://nodejs.org/). If you aren't sure what editor to use, try [VSCode](https://code.visualstudio.com/). It's got great PureScript language support via the [PureScript IDE](https://marketplace.visualstudio.com/items?itemName=nwolverson.ide-purescript) extension. |
| 6 | + |
| 7 | +Done? Ok, we're going to set up this project from scratch so you have a basic idea of how all the parts work together. Don't worry, it won't take long! |
| 8 | + |
| 9 | +## Creating a PureScript web project |
| 10 | + |
| 11 | +Let's create a directory for the project. |
| 12 | + |
| 13 | +```sh |
| 14 | +mkdir purs-react-app && cd purs-react-app |
| 15 | +``` |
| 16 | + |
| 17 | +Initialize `npm` (comes with Node.js). The command below creates a `package.json` file (and a `package-lock.json` file, but that's for `npm` to manage) to track our JavaScript dependencies (i.e. React) and dev tooling (PureScript, etc). We'll also put some useful scripts in here later. |
| 18 | + |
| 19 | +```sh |
| 20 | +npm init -y |
| 21 | +``` |
| 22 | + |
| 23 | +Install PureScript and Spago (project management tool for PureScript). |
| 24 | + |
| 25 | +```sh |
| 26 | +npm i -D purescript spago |
| 27 | +``` |
| 28 | + |
| 29 | +Next, use Spago to set up the PureScript files we need to get started. Spago is installed locally via `npm`, so we need to prefix this command with `npx` to run it. |
| 30 | + |
| 31 | +```sh |
| 32 | +npx spago init |
| 33 | +``` |
| 34 | + |
| 35 | +You should now have a few new directories and files. `packages.dhall` defines the PureScript package source the project will use. The default will be fine. `spago.dhall` is where our PureScript dependencies are defined. Let's add `react-basic-hooks` to that list using Spago. We'll grab `react-basic-dom` too, since we'll need it soon. |
| 36 | + |
| 37 | +```sh |
| 38 | +npx spago install react-basic-hooks react-basic-dom |
| 39 | +``` |
| 40 | + |
| 41 | +We also need to install React and ReactDOM, since `react-basic-hooks` and `react-basic-dom` rely on them, respectively. |
| 42 | + |
| 43 | +```sh |
| 44 | +npm i -S react react-dom |
| 45 | +``` |
| 46 | + |
| 47 | +Ok, we're almost done.. We're now able to write React code using PureScript, but the only thing we can do with it is compile it to JavaScript and stare at it! Which is actually a good time and you'll learn a lot about PureScript in doing so, but let's save that for a future guide. |
| 48 | + |
| 49 | +What we really want next is to start a server with a basic HTML skeleton so we can actually run our React app! |
| 50 | + |
| 51 | +We can set this up pretty quick with existing bundlers like Parcel. Let's install it. |
| 52 | + |
| 53 | +```sh |
| 54 | +npm i -D parcel |
| 55 | +``` |
| 56 | + |
| 57 | +Parcel's a bit picky about project setup. We're building an app, not a JavaScript library. Remove the following line from the `package.json` file to appease it.. :pray: |
| 58 | + |
| 59 | +```json |
| 60 | + "main": "index.js", |
| 61 | +``` |
| 62 | + |
| 63 | +Now we need an entry-point HTML file to point Parcel at. Create the file `src/index.html` with the following content. |
| 64 | + |
| 65 | +```html |
| 66 | +<!DOCTYPE html> |
| 67 | +<html lang="en"> |
| 68 | +<head> |
| 69 | + <meta charset="UTF-8"> |
| 70 | + <meta http-equiv="X-UA-Compatible" content="IE=edge"> |
| 71 | + <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 72 | + <title>PureScript React App</title> |
| 73 | + <script async type="module" src="index.js"></script> |
| 74 | +</head> |
| 75 | +<body> |
| 76 | + <div id="root"></div> |
| 77 | +</body> |
| 78 | +</html> |
| 79 | +``` |
| 80 | + |
| 81 | +Now that HTML file is pointing to a mysterious `index.js` file! Create `src/index.js` with this content. |
| 82 | + |
| 83 | +```js |
| 84 | +import { main } from "../output/Main"; |
| 85 | + |
| 86 | +main(); |
| 87 | +``` |
| 88 | + |
| 89 | +More mysteries! What's this "Main" file with a "main" function inside!? |
| 90 | + |
| 91 | +It's our PureScript! Let's take a look at the `src/Main.purs` file Spago created for us earlier. It probably looks like this. |
| 92 | + |
| 93 | +```purs |
| 94 | +module Main where |
| 95 | +
|
| 96 | +import Prelude |
| 97 | +
|
| 98 | +import Effect (Effect) |
| 99 | +import Effect.Console (log) |
| 100 | +
|
| 101 | +main :: Effect Unit |
| 102 | +main = do |
| 103 | + log "🍝" |
| 104 | +``` |
| 105 | + |
| 106 | +Let's change that `log` call at the bottom so it logs something with deep, personal significance. We'll be that much more proud of our work once we see it in action. |
| 107 | + |
| 108 | +```purs |
| 109 | +main :: Effect Unit |
| 110 | +main = do |
| 111 | + log "guuu" |
| 112 | +``` |
| 113 | + |
| 114 | +## Building and running the app |
| 115 | + |
| 116 | +Building is a two-step process. The PureScript compiler (via Spago) handles the PureScript, emitting it as JavaScript in the `output/` folder. Parcel handles everything from that point on, serving (in dev mode), bundling, and minifying the JavaScript side. |
| 117 | + |
| 118 | +The first thing you should always do is build your PureScript once, like so. |
| 119 | + |
| 120 | +```sh |
| 121 | +npx spago build |
| 122 | +``` |
| 123 | + |
| 124 | +Then you can run Percel in "serve" mode to see it all running in your browser (`ctrl + c` to quit). |
| 125 | + |
| 126 | +```sh |
| 127 | +npx parcel serve src/index.html |
| 128 | +``` |
| 129 | + |
| 130 | +Parcel will give you a URL for your dev server. Open it and take a look at the console. If everything went as planned, you should feel an inspiring sense of accomplishment! Good job! What? You wanted more than an empty white web page? We're getting there, soon, soon! |
| 131 | + |
| 132 | +If you change your HTML or JavaScript files (including by rebuilding the PureScript code), you'll see those changes live-reload in your browser. Nice. |
| 133 | + |
| 134 | +Let's create a production build. |
| 135 | + |
| 136 | +```sh |
| 137 | +npx parcel build --dist-dir dist src/index.html |
| 138 | +``` |
| 139 | + |
| 140 | +This command directs the bundled output to the `dist/` folder. Add this folder to the `.gitignore` Spago made for us earlier! |
| 141 | + |
| 142 | +```sh |
| 143 | +echo "dist/" >> .gitignore |
| 144 | +``` |
| 145 | + |
| 146 | +Ignore Parcel's cache dir as well. |
| 147 | + |
| 148 | +```sh |
| 149 | +echo ".parcel-cache/" >> .gitignore |
| 150 | +``` |
| 151 | + |
| 152 | +You can deploy that `dist/` directory to a server, or upload it to S3 or GitHub Pages. The possibilities are endless! |
| 153 | + |
| 154 | +For example, you can use `npx` to install and run the `serve` tool, a quick and easy way to spin up a server in a given directory (`ctrl + c` to quit). |
| 155 | + |
| 156 | +```sh |
| 157 | +npx serve dist |
| 158 | +``` |
| 159 | + |
| 160 | +Parcel can do quite a bit for you. I recommend [learning more about it](https://parceljs.org/docs/) (or whatever bundler or dev server you choose) when you have a chance. |
| 161 | + |
| 162 | +## Making "future you's" life easier |
| 163 | + |
| 164 | +Remembering all these commands is a pain. Let's make some shortcuts. |
| 165 | + |
| 166 | +You can add scripts to your `package.json` and invoke them with `npm`. These scripts don't need the `npx` prefix. |
| 167 | + |
| 168 | +```json |
| 169 | +{ |
| 170 | + "name": "purs-react-app", |
| 171 | + "version": "1.0.0", |
| 172 | + "description": "", |
| 173 | + "scripts": { |
| 174 | + "test": "spago test", |
| 175 | + "start": "spago build && parcel serve src/index.html", |
| 176 | + "build": "spago build && parcel build --dist-dir dist src/index.html" |
| 177 | + }, |
| 178 | + "keywords": [], |
| 179 | + "author": "", |
| 180 | + "license": "ISC", |
| 181 | + "devDependencies": { |
| 182 | + "parcel": "^2.0.1", |
| 183 | + "purescript": "^0.14.5", |
| 184 | + "spago": "^0.20.3" |
| 185 | + }, |
| 186 | + "dependencies": { |
| 187 | + "react": "^17.0.2", |
| 188 | + "react-dom": "^17.0.2" |
| 189 | + } |
| 190 | +} |
| 191 | +``` |
| 192 | + |
| 193 | +The `start` and `test` scripts are special, you can run them like this: `npm start` or `npm test` |
| 194 | + |
| 195 | +Any other commands you add need the "run" prefix: `npm run build` |
| 196 | + |
| 197 | +We haven't talked about `spago test` yet. You can learn more about general PureScript testing elsewhere. Testing PureScript React components will be a separate guide at some point. |
| 198 | + |
| 199 | +## Rendering your first component |
| 200 | + |
| 201 | +Since this is your first component, I'll write it for you. Don't worry, there's an entire guide on how to write your own components. I'll send you there next! |
| 202 | + |
| 203 | +Copy this content into `src/App/Pages/Home.purs`. You can read it if you like, but don't sweat the details. |
| 204 | + |
| 205 | +```purs |
| 206 | +module App.Pages.Home where |
| 207 | +
|
| 208 | +import Prelude |
| 209 | +
|
| 210 | +import React.Basic.DOM as DOM |
| 211 | +import React.Basic.DOM.Events (capture_) |
| 212 | +import React.Basic.Hooks (Component, component, useState, (/\)) |
| 213 | +import React.Basic.Hooks as React |
| 214 | +
|
| 215 | +type HomeProps = Unit |
| 216 | +
|
| 217 | +mkHome :: Component HomeProps |
| 218 | +mkHome = do |
| 219 | + component "Home" \_props -> React.do |
| 220 | +
|
| 221 | + counter /\ setCounter <- useState 0 |
| 222 | +
|
| 223 | + pure $ DOM.div |
| 224 | + { children: |
| 225 | + [ DOM.h1_ [ DOM.text "Home" ] |
| 226 | + , DOM.p_ [ DOM.text "Try clicking the button!" ] |
| 227 | + , DOM.button |
| 228 | + { onClick: capture_ do |
| 229 | + setCounter (_ + 1) |
| 230 | + , children: |
| 231 | + [ DOM.text "Clicks: " |
| 232 | + , DOM.text (show counter) |
| 233 | + ] |
| 234 | + } |
| 235 | + ] |
| 236 | + } |
| 237 | +``` |
| 238 | + |
| 239 | +If you're already familiar with writing React components in JavaScript and you squint this will probably look familiar! |
| 240 | + |
| 241 | +Now we need to replace the content of `src/Main.purs` so it renders our new component. |
| 242 | + |
| 243 | +```purs |
| 244 | +module Main where |
| 245 | +
|
| 246 | +import Prelude |
| 247 | +
|
| 248 | +import App.Pages.Home (mkHome) |
| 249 | +import Data.Maybe (Maybe(..)) |
| 250 | +import Effect (Effect) |
| 251 | +import Effect.Exception (throw) |
| 252 | +import React.Basic.DOM (render) |
| 253 | +import Web.DOM.NonElementParentNode (getElementById) |
| 254 | +import Web.HTML (window) |
| 255 | +import Web.HTML.HTMLDocument (toNonElementParentNode) |
| 256 | +import Web.HTML.Window (document) |
| 257 | +
|
| 258 | +main :: Effect Unit |
| 259 | +main = do |
| 260 | + root <- getElementById "root" =<< (map toNonElementParentNode $ document =<< window) |
| 261 | + case root of |
| 262 | + Nothing -> |
| 263 | + throw "Root element not found." |
| 264 | + Just r -> do |
| 265 | + home <- mkHome |
| 266 | + render (home unit) r |
| 267 | +``` |
| 268 | + |
| 269 | +Once you're done, run `npm start`. Spago's going to complain that we're now using additional modules we didn't explicitly depend on. Go ahead and run the command it suggests. It should look like this (don't forget to prefix with `npx`). |
| 270 | + |
| 271 | +```sh |
| 272 | +npx spago install exceptions maybe web-dom web-html |
| 273 | +``` |
| 274 | + |
| 275 | +Now run `npm start` again and open the link. |
| 276 | + |
| 277 | +If you see the click counting button you've succeeded! You're ready to head to the [beginner guide](beginner.md) to learn how to build your own components! Good luck! |
0 commit comments