├── LICENSE ├── README.md ├── default.nix ├── index.html ├── package.json └── src └── Main.purs /LICENSE: -------------------------------------------------------------------------------- 1 | The text of this document is licensed under the Creative Commons 2 | Attribution-NonCommercial-ShareAlike 3.0 Unported License: 3 | 4 | http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Reproducible PureScript Quickstart 2 | 3 | This guide offers a simple set of steps to get up and running with 4 | [PureScript](http://www.purescript.org/) with the goal of building 5 | projects in a way that is reproducible and 6 | consistent regardless of the underlying development environment. 7 | 8 | Rather than provide a [ready-made starter app](https://github.com/lumihq/purescript-react-basic), 9 | the objective is to provide some contextual understanding of how to 10 | achieve a streamlined and reliable workflow for your PureScript projects. 11 | 12 | The following steps will take you from a blank slate to a functioning 13 | "Hello World" SPA. 14 | 15 | You'll want to clone and work inside of this repository when following the 16 | instructions below. 17 | 18 | # Step 1: Install Nix 19 | 20 | [Nix](https://nixos.org/nix/) is a powerful package manager for Linux 21 | and other Unix systems that makes package management reliable and reproducible. 22 | 23 | By using Nix as the starting point for your project, you'll be creating 24 | a sandbox for any underlying node/npm dependencies and avoiding polluting 25 | the global environment of your system. 26 | 27 | $ curl https://nixos.org/nix/install | sh 28 | 29 | A Nix channel represents a static set of packages that will not change 30 | in a way that breaks compatibility or affects dependencies. You can find 31 | out what the latest stable channel is 32 | [on the Nix website](https://nixos.wiki/wiki/Nix_Channels). As of today, 33 | the latest stable channel is `18.09`. 34 | 35 | $ nix-channel --add https://nixos.org/channels/nixos-18.09 nixpkgs 36 | 37 | A Nix derivation (build action) is a declarative set of build instructions 38 | which define how to build a specific set of dependent packages in a 39 | predictable way. The `default.nix` file included in this repository 40 | defines a derivation to import a recent (stable) version of `nodejs` 41 | and `psc-package`. 42 | 43 | Invoking `nix-shell` command will build the dependencies of the derivation 44 | specified, and it will start an interactive environment in which all 45 | environment variables defined by the derivation are set accordingly. 46 | The `default.nix` derivation also contains a `PATH` setting to allow 47 | the invocation of locally installed NPM packages from the command-line. 48 | 49 | $ nix-shell default.nix 50 | # npm is now available 51 | $ npm --version 52 | 6.2.0 53 | 54 | # Step 2: Install PureScript and Pulp 55 | 56 | Now that you are inside the Nix environment with NPM installed, you 57 | can move on to installing PureScript and Pulp. 58 | [Pulp](https://github.com/purescript-contrib/pulp) is essentially 59 | a frontend for the PureScript compiler, `purs`. 60 | 61 | $ npm install purescript pulp 62 | ... 63 | + purescript@0.12.1 64 | + pulp@12.3.0 65 | added 133 packages in 11.614s 66 | 67 | 68 | # Step 3: Initialize Your Project 69 | 70 | You can opt to use `bower` or 71 | [psc-package](https://psc-package.readthedocs.io/en/latest/index.html) 72 | for managing dependencies 73 | within your project. Using `psc-package` will allow you to work from 74 | a curated set of packages for which the dependencies have been resolved. 75 | For Haskell users, `psc-package` is more less analogous to `stack` in 76 | the Haskell ecosystem. 77 | 78 | If desired, you can fork the `psc-package` set yourself to guarantee 79 | that no underlying packages versions will change behind the scenes, and 80 | you can also use your forked repository to add custom packages you'd like 81 | to make available in your project. Doing so is just a matter of modifying 82 | `psc-package.json`. 83 | 84 | # Example of a custom package set. 85 | 86 | { 87 | "name": "my-project", 88 | "set": "psc-0.10.2", 89 | "source": "https://github.com/joeuser/package-sets.git", 90 | "depends": [ 91 | "prelude" 92 | ] 93 | } 94 | 95 | The `psc-package` binary is provided by the `default.nix` derivation that 96 | we shelled into earlier. You can use `psc-package` to initialize your 97 | project and install whatever packages you need. Any packages installed 98 | via NPM or `psc-package` will be local to this repository, so you can 99 | always get back to a blank slate just by removing the cloned repository 100 | and starting over from scratch. 101 | 102 | # initialize psc-package project 103 | $ psc-package init 104 | # pin to a known package set 105 | $ sed -i 's/"set":.*/"set": "psc-0.12.3",/' psc-package.json 106 | # add repl support 107 | $ psc-package install psci-support 108 | # add list support 109 | $ psc-package install lists 110 | # add foldable support 111 | $ psc-package install foldable-traversable 112 | # install deps required for building a web app 113 | $ psc-package install effect 114 | $ psc-package install hedwig 115 | 116 | Now you can test your setup via the repl. 117 | 118 | $ psc-package repl 119 | 120 | PSCi, version 0.12.1 121 | Type :? for help 122 | 123 | > import Prelude 124 | > import Data.List 125 | > import Data.Foldable 126 | > foldr (+) 0 (1..10) 127 | 55 128 | 129 | It's worth noting that you can side-step needing both NPM 130 | and psc-package and offload all of your package management 131 | needs to Nix; however, this increases the complexity on the 132 | Nix side of the equation and falls outside of the scope of 133 | this document. 134 | 135 | That said, here are some resources if you want to explore 136 | doing everything in Nix. 137 | 138 | * [node2nix](https://github.com/svanderburg/node2nix) 139 | * [Nix-ify Your Psc-Package Dependencies](https://qiita.com/kimagure/items/85a64437f9af78398638) 140 | 141 | # Step 4: Supporting Web Libraries 142 | 143 | The final step is to add libraries to facilitate developing 144 | an [SPA](https://en.wikipedia.org/wiki/Single-page_application) 145 | in PureScript. For this example, we'll use 146 | [Hedwig](https://github.com/utkarshkukreti/purescript-hedwig), 147 | a declarative PureScript library for building web applications, 148 | as Hedwig follows [TEA](https://guide.elm-lang.org/architecture/), 149 | a familiar design pattern for developing SPA; however, you should 150 | explore the list of available libraries and choose whatever best 151 | fits your needs. 152 | 153 | # install the required npm packages from package.json 154 | $ npm install 155 | # watch for changes and re-compile as needed 156 | $ npm start 157 | 158 | From here, you can open `index.html`, and you should see Hedwig's 159 | example counter application running in your browser. Pulp will recompile 160 | your project whenever you make changes to your code, so you get hot 161 | reloading for free! 162 | 163 | # Step 5: Deploy! 164 | 165 | The `package.json` file includes `uglify` and a script hook to minify 166 | the Javascript emitted from `purs` and get it ready for production. 167 | The `pulp` invocation inside of `package.json` includes the `--optimise` 168 | flag which performs dead code elimination to minimize the footprint of 169 | the Javascript that's generated. 170 | 171 | $ npm run dist 172 | 173 | > purescript-quickstart@0.1.0 dist /private/tmp/howto 174 | > mkdir -p dist/js && uglifyjs js/*.js -m -o dist/js/index.js && cp index.html dist/ 175 | 176 | $ tree dist 177 | dist 178 | ├── index.html 179 | └── js 180 | └── index.js 181 | 182 | 1 directory, 2 files 183 | 184 | $ ls -lh dist/js/index.js | awk '{print $4}' 185 | 44K 186 | 187 | The contents of the `dist` directory are ready to 188 | [deploy](https://github.com/stojanovic/scottyjs) to your favorite CDN. 189 | 190 | # Step 6: Learn More PureScript 191 | 192 | Put simply, PureScript 193 | [sucks the least](https://www.reddit.com/r/purescript/comments/6g6brx/why_purescript/dinwty2/) 194 | out of all the FP frontend languages available. Take advantage of 195 | some of the excellent resources available to further your understanding 196 | of the language, and go 197 | [build something](http://holdenlee.github.io/blog/posts/programming/purescript/snake-in-purescript.html)! 198 | 199 | Helpful resources: 200 | 201 | * [PureScript by Example](https://leanpub.com/purescript/read) 202 | * [PureScript Resources](https://purescript-resources.readthedocs.io/en/latest/index.html) 203 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | # This imports the nix package collection, 2 | # so we can access the `pkgs` and `stdenv` variables 3 | with import {}; 4 | 5 | # Make a new "derivation" that represents our shell 6 | stdenv.mkDerivation { 7 | name = "purs-sandbox"; 8 | 9 | # The packages in the `buildInputs` list will be added to the PATH in our shell 10 | buildInputs = [ 11 | # see https://nixos.org/nixos/packages.html to search for more 12 | pkgs.nodejs-10_x 13 | pkgs.psc-package 14 | ]; 15 | 16 | shellHook = '' 17 | export PATH="$PWD/node_modules/.bin/:$PATH" 18 | ''; 19 | } 20 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My App 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "purescript-quickstart", 3 | "version": "0.1.0", 4 | "scripts": { 5 | "start": "pulp --watch browserify --optimise --to js/index.js", 6 | "dist": "mkdir -p dist/js && uglifyjs js/*.js -m -o dist/js/index.js && cp index.html dist/" 7 | }, 8 | "dependencies": { 9 | "pulp": "^12.4.2", 10 | "purescript": "^0.12.5", 11 | "purescript-hedwig": "^0.1.0", 12 | "uglify": "^0.1.5" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Main.purs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Hedwig as H 7 | import Hedwig ((:>)) 8 | 9 | type Model = Int 10 | 11 | init :: Model 12 | init = 0 13 | 14 | data Msg = Increment | Decrement 15 | 16 | update :: Model -> Msg -> Model 17 | update model = case _ of 18 | Increment -> model + 1 19 | Decrement -> model - 1 20 | 21 | view :: Model -> H.Html Msg 22 | view model = H.main [H.id "main"] [ 23 | H.button [H.onClick Decrement] [H.text "-"], 24 | H.text (show model), 25 | H.button [H.onClick Increment] [H.text "+"] 26 | ] 27 | 28 | main :: Effect Unit 29 | main = do 30 | H.mount "main" { 31 | init: init :> [], 32 | update: \msg model -> update msg model :> [], 33 | view 34 | } 35 | --------------------------------------------------------------------------------