├── static
├── favicon.ico
├── purescript.png
└── index.html
├── .gitignore
├── src
├── App
│ ├── Config.purs
│ ├── Config.js
│ ├── View
│ │ ├── NotFound.purs
│ │ ├── Homepage.purs
│ │ └── Layout.purs
│ ├── Routes.purs
│ ├── State.purs
│ └── Events.purs
└── Main.purs
├── support
└── entry.js
├── bower.json
├── LICENSE
├── package.json
├── README.md
└── webpack.config.js
/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexmingoia/pux-starter-app/HEAD/static/favicon.ico
--------------------------------------------------------------------------------
/static/purescript.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexmingoia/pux-starter-app/HEAD/static/purescript.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | bower_components/
3 | output/
4 | dist/
5 | static/dist/
6 | .psci_modules
7 | npm-debug.log
8 | **DS_Store
9 |
--------------------------------------------------------------------------------
/src/App/Config.purs:
--------------------------------------------------------------------------------
1 | module App.Config where
2 |
3 | type Config =
4 | { title :: String
5 | , public_path :: String
6 | }
7 |
8 | foreign import config :: Config
9 |
--------------------------------------------------------------------------------
/src/App/Config.js:
--------------------------------------------------------------------------------
1 | exports.config = {
2 | title: 'Pux Starter App',
3 | public_path: process.env.NODE_ENV === 'production'
4 | ? '/dist/'
5 | : 'http://localhost:8080/dist/'
6 | }
7 |
--------------------------------------------------------------------------------
/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Pux Starter App
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/App/View/NotFound.purs:
--------------------------------------------------------------------------------
1 | module App.View.NotFound where
2 |
3 | import Data.Function (($))
4 | import Pux.DOM.HTML (HTML)
5 | import Text.Smolder.HTML (div, h2)
6 | import Text.Smolder.Markup (text)
7 |
8 | view :: ∀ st ev. st -> HTML ev
9 | view st = div $ h2 $ text "404 Not Found"
10 |
--------------------------------------------------------------------------------
/support/entry.js:
--------------------------------------------------------------------------------
1 | var ClientEntry = require('../src/Main.purs');
2 | var app = ClientEntry.main(window.location.pathname)(window.__puxLastState || ClientEntry.initialState)()
3 |
4 | app.state.subscribe(function (state) {
5 | window.__puxLastState = state;
6 | });
7 |
8 | // If hot-reloading, hook into each state change and re-render using the last
9 | // state.
10 | if (module.hot) {
11 | module.hot.accept();
12 | }
13 |
--------------------------------------------------------------------------------
/src/App/Routes.purs:
--------------------------------------------------------------------------------
1 | module App.Routes where
2 |
3 | import Data.Function (($))
4 | import Data.Functor ((<$))
5 | import Data.Maybe (fromMaybe)
6 | import Pux.Router (end, router)
7 |
8 | data Route = Home | NotFound String
9 |
10 | match :: String -> Route
11 | match url = fromMaybe (NotFound url) $ router url $
12 | Home <$ end
13 |
14 | toURL :: Route -> String
15 | toURL (NotFound url) = url
16 | toURL (Home) = "/"
17 |
--------------------------------------------------------------------------------
/src/App/State.purs:
--------------------------------------------------------------------------------
1 | module App.State where
2 |
3 | import App.Config (config)
4 | import App.Routes (Route, match)
5 | import Data.Newtype (class Newtype)
6 |
7 | newtype State = State
8 | { title :: String
9 | , route :: Route
10 | , loaded :: Boolean
11 | }
12 |
13 | derive instance newtypeState :: Newtype State _
14 |
15 | init :: String -> State
16 | init url = State
17 | { title: config.title
18 | , route: match url
19 | , loaded: false
20 | }
21 |
--------------------------------------------------------------------------------
/src/App/Events.purs:
--------------------------------------------------------------------------------
1 | module App.Events where
2 |
3 | import App.Routes (Route)
4 | import App.State (State(..))
5 | import Data.Function (($))
6 | import Network.HTTP.Affjax (AJAX)
7 | import Pux (EffModel, noEffects)
8 |
9 | data Event = PageView Route
10 |
11 | type AppEffects fx = (ajax :: AJAX | fx)
12 |
13 | foldp :: ∀ fx. Event -> State -> EffModel State Event (AppEffects fx)
14 | foldp (PageView route) (State st) = noEffects $ State st { route = route, loaded = true }
15 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pux-starter-app",
3 | "homepage": "https://github.com/alexmingoia/pux-starter-app",
4 | "authors": [
5 | "Alex Mingoia "
6 | ],
7 | "description": "Starter Pux application using webpack with hot-reloading.",
8 | "main": "support/entry.client.js",
9 | "license": "BSD3",
10 | "dependencies": {
11 | "purescript-pux": "^11.0.0",
12 | "purescript-css": "^3.0.0",
13 | "purescript-aff": "^3.0.0",
14 | "purescript-affjax": "^4.0.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/App/View/Homepage.purs:
--------------------------------------------------------------------------------
1 | module App.View.Homepage where
2 |
3 | import App.Events (Event)
4 | import App.State (State)
5 | import Control.Bind (discard)
6 | import Data.Function (($))
7 | import Pux.DOM.HTML (HTML)
8 | import Text.Smolder.HTML (a, div, h1)
9 | import Text.Smolder.HTML.Attributes (href, className)
10 | import Text.Smolder.Markup ((!), text)
11 |
12 | view :: State -> HTML Event
13 | view s =
14 | div do
15 | h1 $ text "Pux"
16 | a ! className "guide" ! href "https://www.purescript-pux.org/" $ text "Guide"
17 | a ! className "github" ! href "https://github.com/alexmingoia/purescript-pux/" $ text "GitHub"
18 |
--------------------------------------------------------------------------------
/src/Main.purs:
--------------------------------------------------------------------------------
1 | module Main where
2 |
3 | import Prelude
4 | import App.Events (AppEffects, Event(..), foldp)
5 | import App.Routes (match)
6 | import App.State (State, init)
7 | import App.View.Layout (view)
8 | import Control.Monad.Eff (Eff)
9 | import DOM (DOM)
10 | import DOM.HTML (window)
11 | import DOM.HTML.Types (HISTORY)
12 | import Pux (CoreEffects, App, start)
13 | import Pux.DOM.Events (DOMEvent)
14 | import Pux.DOM.History (sampleURL)
15 | import Pux.Renderer.React (renderToDOM)
16 | import Signal ((~>))
17 |
18 | type WebApp = App (DOMEvent -> Event) Event State
19 |
20 | type ClientEffects = CoreEffects (AppEffects (history :: HISTORY, dom :: DOM))
21 |
22 | main :: String -> State -> Eff ClientEffects WebApp
23 | main url state = do
24 | -- | Create a signal of URL changes.
25 | urlSignal <- sampleURL =<< window
26 |
27 | -- | Map a signal of URL changes to PageView actions.
28 | let routeSignal = urlSignal ~> \r -> PageView (match r)
29 |
30 | -- | Start the app.
31 | app <- start
32 | { initialState: state
33 | , view
34 | , foldp
35 | , inputs: [routeSignal] }
36 |
37 | -- | Render to the DOM
38 | renderToDOM "#app" app.markup app.input
39 |
40 | -- | Return app to be used for hot reloading logic in support/client.entry.js
41 | pure app
42 |
43 | initialState :: State
44 | initialState = init "/"
45 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016, Alexander C. Mingoia
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 | * Redistributions of source code must retain the above copyright
7 | notice, this list of conditions and the following disclaimer.
8 | * Redistributions in binary form must reproduce the above copyright
9 | notice, this list of conditions and the following disclaimer in the
10 | documentation and/or other materials provided with the distribution.
11 | * Neither the name of the nor the
12 | names of its contributors may be used to endorse or promote products
13 | derived from this software without specific prior written permission.
14 |
15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY
19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pux-starter-app",
3 | "version": "13.0.0",
4 | "description": "Pux starter app with hot-reloading and isomorphic rendering and routing.",
5 | "main": "support/index.js",
6 | "keywords": [
7 | "pux",
8 | "purescript-pux",
9 | "boilerplate",
10 | "starter-app"
11 | ],
12 | "scripts": {
13 | "postinstall": "bower cache clean && bower install",
14 | "clean": "rimraf static/dist",
15 | "build": "npm run clean && webpack --config ./webpack.config.js --progress --profile --colors",
16 | "watch": "npm run clean && webpack-dev-server --content-base static/ --hot --inline --config webpack.config.js",
17 | "serve": "npm run build && serve -s static",
18 | "start": "npm run watch",
19 | "test": "echo \"Error: no test specified\" && exit 1"
20 | },
21 | "repository": {
22 | "type": "git",
23 | "url": "git://github.com/alexmingoia/pux-starter-app.git"
24 | },
25 | "author": "Alexander C. Mingoia",
26 | "license": "BSD-3-Clause",
27 | "bugs": {
28 | "url": "https://github.com/alexmingoia/pux-starter-app/issues"
29 | },
30 | "engines": {
31 | "node": "^6.0.0"
32 | },
33 | "dependencies": {
34 | "bower": "^1.7.9",
35 | "preact": "^8.2.1",
36 | "preact-compat": "^3.17.0",
37 | "purescript": "^0.11.6",
38 | "purescript-psa": "^0.5.1",
39 | "purs-loader": "^3.1.0",
40 | "rimraf": "^2.5.2",
41 | "serve": "^5.2.4",
42 | "webpack": "^2.7.0",
43 | "webpack-dev-server": "^2.7.1",
44 | "webpack-node-externals": "^1.5.4",
45 | "xhr2": "^0.1.3"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # pux-starter-app
2 |
3 | The [starter app](http://github.com/alexmingoia/pux-starter-app) is a skeleton
4 | Pux project configured with webpack and supporting hot-reload for rapid
5 | development.
6 |
7 | For isomorphic rendering and routing a more complex configuration is available in the
8 | [`isomorphic`](https://github.com/alexmingoia/pux-starter-app/tree/isomorphic) branch.
9 |
10 | ## Installation
11 |
12 | Clone the repository and run `npm install` to get started:
13 |
14 | ```sh
15 | git clone git://github.com/alexmingoia/pux-starter-app.git my-awesome-pux-app
16 | cd my-awesome-pux-app
17 | npm install
18 | npm start
19 | ```
20 |
21 | After compiling the app should be available at `http://localhost:8080`.
22 |
23 | ### Directory structure
24 |
25 | - `src`: Application source code.
26 | - `src/App/Config.js`: Configuration values.
27 | - `src/App/Config.purs`: Configuration type.
28 | - `src/App/Events.purs`: Application event type and foldp function.
29 | - `src/App/Routes.purs`: Routes.
30 | - `src/App/State.purs`: Application state type and init function.
31 | - `src/App/View/Homepage.purs`: Home page.
32 | - `src/App/View/Layout.purs`: App layout.
33 | - `src/App/View/NotFound.purs`: 404 page.
34 | - `src/Main.purs`: PureScript entry point.
35 | - `static`: Static files served with application.
36 | - `support`: Support files for building.
37 | - `support/entry.js`: Webpack entry point. Handles hot reloading.
38 | - `bower.json`: Bower package configuration.
39 | - `package.json`: Node package configuration.
40 | - `webpack.config.js`: Webpack configuration.
41 |
42 | ### NPM scripts
43 |
44 | #### watch
45 |
46 | `npm start` or `npm run watch` will start a development server, which
47 | hot-reloads your application when sources changes.
48 |
49 | #### serve
50 |
51 | `NODE_ENV=production npm run serve` builds your application and starts a
52 | production server.
53 |
54 | #### build
55 |
56 | `npm run build` builds application client and server bundles.
57 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const appConfig = require('./src/App/Config.js').config
2 | const path = require('path')
3 | const webpack = require('webpack')
4 | const isProd = process.env.NODE_ENV === 'production'
5 |
6 | const entries = [path.join(__dirname, 'support/entry.js')]
7 |
8 | const plugins = [
9 | new webpack.DefinePlugin({
10 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
11 | })
12 | ]
13 |
14 | if (isProd) {
15 | plugins.push(
16 | new webpack.LoaderOptionsPlugin({
17 | minimize: true,
18 | debug: false
19 | })
20 | )
21 | }
22 |
23 | module.exports = {
24 | entry: entries,
25 | context: __dirname,
26 | target: 'web',
27 | output: {
28 | path: path.join(__dirname, 'static', 'dist'),
29 | filename: 'bundle.js',
30 | publicPath: appConfig.public_path
31 | },
32 | module: {
33 | loaders: [
34 | {
35 | test: /\.purs$/,
36 | loader: 'purs-loader',
37 | exclude: /node_modules/,
38 | query: isProd ? {
39 | bundle: true,
40 | bundleOutput: 'static/dist/bundle.js'
41 | } : {
42 | psc: 'psa',
43 | pscIde: true
44 | }
45 | }
46 | ],
47 | },
48 | plugins: plugins,
49 | resolveLoader: {
50 | modules: [
51 | path.join(__dirname, 'node_modules')
52 | ]
53 | },
54 | resolve: {
55 | alias: {
56 | 'react': 'preact-compat',
57 | 'react-dom': 'preact-compat',
58 | 'create-react-class': 'preact-compat/lib/create-react-class'
59 | },
60 | modules: [
61 | 'node_modules',
62 | 'bower_components'
63 | ],
64 | extensions: ['.js', '.purs']
65 | },
66 | performance: { hints: false },
67 | stats: {
68 | hash: false,
69 | timings: false,
70 | version: false,
71 | assets: false,
72 | errors: true,
73 | colors: false,
74 | chunks: false,
75 | children: false,
76 | cached: false,
77 | modules: false,
78 | chunkModules: false
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/App/View/Layout.purs:
--------------------------------------------------------------------------------
1 | module App.View.Layout where
2 |
3 | import App.View.Homepage as Homepage
4 | import App.View.NotFound as NotFound
5 | import App.Routes (Route(NotFound, Home))
6 | import App.State (State(..))
7 | import App.Events (Event)
8 | import CSS (CSS, fromString, (?), fontSize, display, inlineBlock, marginTop, marginRight, marginLeft, px, value, key, color, backgroundColor, padding, borderRadius)
9 | import CSS.Border (border, solid)
10 | import CSS.TextAlign (center, textAlign)
11 | import CSS.Text (textDecoration, noneTextDecoration, letterSpacing)
12 | import CSS.Text.Transform (textTransform, uppercase)
13 | import Color (rgb)
14 | import Control.Bind (discard)
15 | import Data.Function (($), (#))
16 | import Pux.DOM.HTML (HTML, style)
17 | import Text.Smolder.HTML (div)
18 | import Text.Smolder.HTML.Attributes (className)
19 | import Text.Smolder.Markup ((!))
20 |
21 | view :: State -> HTML Event
22 | view (State st) =
23 | div ! className "app" $ do
24 | style css
25 |
26 | case st.route of
27 | (Home) -> Homepage.view (State st)
28 | (NotFound url) -> NotFound.view (State st)
29 |
30 | css :: CSS
31 | css = do
32 | let green = rgb 14 196 172
33 | blue = rgb 14 154 196
34 | white = rgb 250 250 250
35 |
36 | fromString "body" ? do
37 | backgroundColor (rgb 0 20 30)
38 | key (fromString "font-family") (value "-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif")
39 | color white
40 | textAlign center
41 |
42 | fromString "h1" ? do
43 | fontSize (48.0 #px)
44 | marginTop (48.0 #px)
45 | textTransform uppercase
46 | letterSpacing (6.0 #px)
47 |
48 | fromString "a" ? do
49 | display inlineBlock
50 | borderRadius (2.0 #px) (2.0 #px) (2.0 #px) (2.0 #px)
51 | padding (6.0 #px) (6.0 #px) (6.0 #px) (6.0 #px)
52 | textDecoration noneTextDecoration
53 |
54 | fromString ".guide" ? do
55 | border solid (2.0 #px) green
56 | color green
57 | marginRight (10.0 #px)
58 |
59 | fromString ".guide:hover" ? do
60 | backgroundColor green
61 | color white
62 |
63 | fromString ".github" ? do
64 | border solid (2.0 #px) blue
65 | color blue
66 | marginLeft (10.0 #px)
67 |
68 | fromString ".github:hover" ? do
69 | backgroundColor blue
70 | color white
71 |
--------------------------------------------------------------------------------