├── 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 | --------------------------------------------------------------------------------