├── ps_hmr.gif
├── .gitignore
├── src
├── index.html
├── main.js
├── App.purs
└── Counter.purs
├── bower.json
├── LICENSE.md
├── package.json
├── README.md
└── webpack.config.babel.js
/ps_hmr.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sectore/purescript-webpack-vanilla-hmr/HEAD/ps_hmr.gif
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | bower_components
3 | dist
4 | output
5 | .DS_Store
6 | typings
7 | npm-debug.log
8 | .psc-ide-port
9 | yarn-error.log
10 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PureScript - Webpack - vanilla HMR
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import App from './App.purs';
2 | import {initialState} from './Counter.purs';
3 | // vanilla hot module reloading
4 | // @see https://webpack.js.org/guides/hot-module-replacement/
5 | if(module.hot) {
6 | const app = App.main(window.__puxLastState || initialState)();
7 | // don't lose state while HMR
8 | app.state.subscribe(function (st) {
9 | window.__puxLastState = st;
10 | });
11 | module.hot.accept();
12 | } else {
13 | App.main(initialState)();
14 | }
15 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "purescript-webpack-hmr",
3 | "description": "PureScript + Vanilla HMR",
4 | "main": "index.js",
5 | "authors": [
6 | "Jens Krause - jkrause.io"
7 | ],
8 | "license": "MIT",
9 | "keywords": [
10 | "PureScript",
11 | "Webpack"
12 | ],
13 | "moduleType": [],
14 | "homepage": "",
15 | "private": true,
16 | "ignore": [
17 | "**/.*",
18 | "node_modules",
19 | "bower_components",
20 | "test",
21 | "tests"
22 | ],
23 | "dependencies": {},
24 | "devDependencies": {
25 | "purescript-pux": "^12.0.0"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/App.purs:
--------------------------------------------------------------------------------
1 | module App where
2 |
3 | import Prelude
4 | import Control.Monad.Eff (Eff)
5 | import Pux (App, CoreEffects, start)
6 | import Pux.DOM.Events (DOMEvent)
7 | import Pux.Renderer.React (renderToDOM)
8 | import App.Counter (Action, State, update, view)
9 |
10 | type WebApp = App (DOMEvent -> Action) Action State
11 |
12 | main :: forall e . State -> Eff (CoreEffects e) WebApp
13 | main state = do
14 | app <- start
15 | { initialState: state
16 | , foldp: update
17 | , view
18 | , inputs: []
19 | }
20 | _ <- renderToDOM "#app" app.markup app.input
21 | pure app
22 |
--------------------------------------------------------------------------------
/src/Counter.purs:
--------------------------------------------------------------------------------
1 | module App.Counter where
2 |
3 | import Prelude
4 | import Pux.DOM.Events (onClick)
5 | import Pux (EffModel, noEffects)
6 | import Pux.DOM.HTML (HTML)
7 | import Text.Smolder.HTML (button, h1, h3, div) as S
8 | import Text.Smolder.Markup (text) as S
9 | import Text.Smolder.Markup ((#!))
10 |
11 | data Action = Increment | Decrement
12 |
13 | type State = Int
14 |
15 | initialState :: State
16 | initialState = 0
17 |
18 | update :: forall e . Action -> State -> EffModel State Action e
19 | update Increment state = noEffects $ state + 1
20 | update Decrement state = noEffects $ state - 1
21 |
22 | view :: State -> HTML Action
23 | view state =
24 | S.div do
25 | S.h1 $ S.text "PureScript + Vanilla HMR"
26 | S.h3 $ S.text $ ("Count: " <> show state)
27 | S.div do
28 | S.button #! onClick (const Increment) $ S.text "Increment"
29 | S.button #! onClick (const Decrement) $ S.text "Decrement"
30 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Jens Krause
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "purescript-webpack-vanilla-hmr",
3 | "version": "0.0.1",
4 | "description": "PureScript + Vanilla HMR",
5 | "main": "index.js",
6 | "scripts": {
7 | "postinstall": "./node_modules/.bin/bower cache clean && ./node_modules/.bin/bower install",
8 | "start": "npm run server:dev",
9 | "server:webpack": "./node_modules/.bin/webpack-dev-server --config webpack.config.babel.js --progress",
10 | "server:dev": "rimraf output && NODE_ENV=development npm run server:webpack",
11 | "server:prod": "rimraf output && NODE_ENV=production npm run server:webpack",
12 | "build:prod": "rimraf dist && mkdir dist && NODE_ENV=production ./node_modules/.bin/webpack --config webpack.config.babel.js",
13 | "test": "echo \"Error: no test specified\" && exit 1"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/sectore/purescript-webpack-vanilla-hmr.git"
18 | },
19 | "keywords": [
20 | "PureScript",
21 | "webpack",
22 | "hmr"
23 | ],
24 | "author": {
25 | "name": "Jens Krause",
26 | "email": "email@jkrause.io"
27 | },
28 | "contributors": [
29 | {
30 | "name": "Kurt Harriger",
31 | "email": "kurtharriger@gmail.com"
32 | }
33 | ],
34 | "license": "MIT",
35 | "bugs": {
36 | "url": "https://github.com/sectore/purescript-webpack-vanilla-hmr/issues"
37 | },
38 | "homepage": "https://github.com/sectore/purescript-webpack-vanilla-hmr#readme",
39 | "dependencies": {
40 | "create-react-class": "^15.6.3",
41 | "react": "^16.3.1",
42 | "react-dom": "^16.3.1"
43 | },
44 | "devDependencies": {
45 | "babel-core": "^6.24.0",
46 | "babel-loader": "^7.1.4",
47 | "babel-plugin-transform-es2015-destructuring": "^6.23.0",
48 | "babel-preset-es2015": "^6.24.0",
49 | "babel-preset-react": "^6.23.0",
50 | "babel-preset-stage-2": "^6.22.0",
51 | "bower": "^1.8.0",
52 | "css-loader": "^0.28.11",
53 | "html-webpack-plugin": "^3.2.0",
54 | "purescript": "^0.11.7",
55 | "purescript-psa": "^0.6.0",
56 | "purs-loader": "^3.1.4",
57 | "rimraf": "^2.6.1",
58 | "source-map-loader": "^0.2.0",
59 | "style-loader": "^0.20.3",
60 | "uglifyjs-webpack-plugin": "^1.2.4",
61 | "webpack": "^4.5.0",
62 | "webpack-cli": "^2.0.14",
63 | "webpack-dev-server": "^3.1.3"
64 | },
65 | "babel": {
66 | "presets": [
67 | "es2015",
68 | "stage-2",
69 | "react"
70 | ]
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # purescript-webpack-vanilla-hmr
2 |
3 | Simple + fast build of **[PureScript](purescript.org)** with **[Webpack](https://webpack.js.org/)** and **[Hot Module Replacement (HMR)](https://webpack.js.org/guides/hot-module-replacement/)**. No [Pulp](https://github.com/bodil/pulp), no [Gulp](http://gulpjs.com/) or anything else - just [webpack](https://webpack.github.io/).
4 |
5 | 
6 |
7 | ## Why?
8 |
9 | > "I strongly suggest you to consider using vanilla HMR API instead of React Hot Loader, React Transform, or other similar projects. It’s just so much simpler—at least, it is today."
10 |
11 | _Quote by Dan Abranov (Creator of [Redux](redux.js.org), [React Hot Loader](https://github.com/gaearon/react-hot-loader) etc.)_
12 |
13 | Why not the using the same stuff for building PureScript application today?
14 |
15 | ## Do I need this to run `Pux` with `HMR`?
16 |
17 | To demonstrate the feature of `HMR` [the example app](./src/App.purs) uses [`Pux`](https://github.com/alexmingoia/purescript-pux) under the hood. However, Pux's `starter-app` [has already merged](https://github.com/alexmingoia/pux-starter-app/pull/5) the HMR solution from here. So you don't need to implement this `HMR` _thing_ into your Pux app.
18 |
19 | ## Installation
20 |
21 | It's recommended to use [yarn](https://yarnpkg.com/)
22 |
23 | ```bash
24 | yarn install
25 | yarn start
26 | ```
27 |
28 | Open http://localhost:3000/
29 |
30 | ## Acknowledge
31 |
32 | - Documentation of webpack: ["Hot Module Replacement" (HMR)](https://webpack.github.io/docs/hot-module-replacement.html)
33 | - ["Hot Reloading in React or, an Ode to Accidental Complexity"](https://medium.com/@dan_abramov/hot-reloading-in-react-1140438583bf#.vnlkto5p1) by Dan Abramov
34 | - Redux PR 1455 ["RFC: remove React Transform from examples"](https://github.com/reactjs/redux/pull/1455)
35 | - [purs-loader](https://github.com/ethul/purs-loader), [purescript-webpack-plugin](https://www.npmjs.com/package/purescript-webpack-plugin) and [purescript-webpack-example](https://github.com/ethul/purescript-webpack-example/blob/master/webpack.config.js) by [ethul](https://github.com/ethul)
36 | - [Webpack and React tutorial - Taking the next steps](http://www.christianalfoni.com/articles/2015_10_01_Taking-the-next-step-with-react-and-webpack) by [Christian Alfoni](https://github.com/christianalfoni)
37 | - [An Angular 2 Starter kit featuring Angular 2](https://github.com/AngularClass/angular2-webpack-starter) by [AngularClass](https://github.com/AngularClass)
38 | - Counter Example of [Pux](https://github.com/alexmingoia/purescript-pux), an awesome PureScript FRP interface to React by [Alex Mingoia](https://github.com/alexmingoia)
39 |
40 | ### License
41 | [MIT](./LICENSE)
42 |
43 | Have fun :) -- Jens Krause -- [jkrause.io](http://jkrause.io)
44 |
--------------------------------------------------------------------------------
/webpack.config.babel.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | const {
4 | DefinePlugin,
5 | ProgressPlugin,
6 | NoEmitOnErrorsPlugin,
7 | HotModuleReplacementPlugin
8 | } = require('webpack');
9 |
10 | import HtmlWebpackPlugin from 'html-webpack-plugin';
11 | import NamedModulesPlugin from 'webpack/lib/NamedModulesPlugin';
12 | import UglifyJsPlugin from 'uglifyjs-webpack-plugin';
13 |
14 | const nodeEnv = process.env.NODE_ENV || 'development';
15 | const isProd = nodeEnv === 'production';
16 |
17 | const paths = {
18 | src: path.join(__dirname, '/src/'),
19 | output: path.join(__dirname, '/dist/'),
20 | public: '/'
21 | }
22 |
23 | console.log("production build: ", isProd);
24 |
25 | module.exports = {
26 | devtool: isProd ? 'nosources-source-map' : 'cheap-module-eval-source-map',
27 | mode: nodeEnv,
28 | output: {
29 | path: paths.output,
30 | publicPath: paths.public,
31 | ...(isProd ? {
32 | filename: '[name]-[hash].min.js',
33 | } : {
34 | pathinfo: true,
35 | filename: '[name].js',
36 | }
37 | )
38 | },
39 | plugins: [
40 | new ProgressPlugin(),
41 | new HtmlWebpackPlugin({
42 | template: 'src/index.html',
43 | inject: 'body',
44 | filename: 'index.html',
45 | minify: {
46 | collapseWhitespace: isProd
47 | }
48 | }),
49 | new DefinePlugin({
50 | 'process.env.NODE_ENV': JSON.stringify(nodeEnv)
51 | }),
52 | new NamedModulesPlugin(),
53 | new HotModuleReplacementPlugin(),
54 | ...(isProd ? [
55 | new NoEmitOnErrorsPlugin(),
56 | new UglifyJsPlugin({
57 | sourceMap: false,
58 | beautify: false,
59 | comments: false,
60 | compress: {
61 | drop_console: true,
62 | dead_code: true,
63 | warnings: false
64 | }
65 | })
66 | ] : []
67 | )
68 | ],
69 | entry: [
70 | path.join(__dirname, 'src/main.js')
71 | ],
72 | resolve: {
73 | extensions: [ '.js', '.purs']
74 | },
75 | module: {
76 | rules: [
77 | {
78 | test: /\.js$/,
79 | exclude: /(node_modules|bower_components)/,
80 | loader: 'babel-loader'
81 | },
82 | {
83 | test: /\.purs$/,
84 | loader: 'purs-loader',
85 | exclude: /node_modules/,
86 |
87 | query: {
88 | psc: 'psa',
89 | src: [
90 | path.join('src', '**', '*.purs'),
91 | path.join('bower_components', 'purescript-*', 'src', '**', '*.purs')
92 | ],
93 | bundle: isProd,
94 | watch: !isProd
95 | }
96 | }
97 | ]
98 | },
99 | devServer: {
100 | hot: !isProd,
101 | contentBase: paths.src,
102 | port: 3000,
103 | host: 'localhost',
104 | historyApiFallback: true,
105 | watchOptions: {
106 | aggregateTimeout: 300,
107 | poll: 1000
108 | },
109 | stats: 'minimal',
110 | }
111 | };
112 |
--------------------------------------------------------------------------------