--------------------------------------------------------------------------------
/routes/index/reducers/index.js:
--------------------------------------------------------------------------------
1 | module.exports = function(state, action) {
2 | state = state || {
3 | value: 0
4 | };
5 |
6 | switch (action.type) {
7 | case 'INCREMENT':
8 | return {
9 | value: state.value + 1
10 | };
11 | case 'DECREMENT':
12 | return {
13 | value: state.value - 1
14 | };
15 | default:
16 | return state;
17 | }
18 | };
--------------------------------------------------------------------------------
/routes/server-side-redux/reducers/index.js:
--------------------------------------------------------------------------------
1 | module.exports = function(state, action) {
2 | state = state || {
3 | value: 0
4 | };
5 |
6 | switch (action.type) {
7 | case 'INCREMENT':
8 | return {
9 | value: state.value + 1
10 | };
11 | case 'DECREMENT':
12 | return {
13 | value: state.value - 1
14 | };
15 | default:
16 | return state;
17 | }
18 | };
--------------------------------------------------------------------------------
/routes/server-side-redux/route.js:
--------------------------------------------------------------------------------
1 | var view = require('./index');
2 |
3 | module.exports = function(req, res) {
4 | /*
5 | * We can create the initial state of the redux store on the server.
6 | * In this example, the initial state of our store is retrieved from
7 | * the request parameters.
8 | */
9 | var value = parseInt(req.params.value);
10 |
11 | res.marko(view, {
12 | $global: {
13 | PRELOADED_STATE: {
14 | value: value,
15 | },
16 | },
17 | });
18 | };
19 |
--------------------------------------------------------------------------------
/routes/server-side-redux/store.js:
--------------------------------------------------------------------------------
1 | var redux = require('redux');
2 | var counter = require('./reducers');
3 |
4 | /*
5 | * We use an Immediately-Invoked Function Expression (IIFE) to hold our
6 | * redux store. This allows us to create a store client-side using the
7 | * preloaded state from the server.
8 | */
9 | module.exports = (function() {
10 | var store;
11 |
12 | function initialize(preloadedState) {
13 | store = redux.createStore(counter, preloadedState);
14 | return store;
15 | }
16 |
17 | function getStore() {
18 | return store;
19 | }
20 |
21 | return {
22 | initialize: initialize,
23 | getStore: getStore,
24 | };
25 | })();
26 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node" : true,
3 | "esnext" : true,
4 | "browser" : true,
5 | "boss" : false,
6 | "curly": false,
7 | "debug": false,
8 | "devel": false,
9 | "eqeqeq": true,
10 | "evil": true,
11 | "forin": false,
12 | "immed": true,
13 | "laxbreak": false,
14 | "newcap": true,
15 | "noarg": true,
16 | "noempty": false,
17 | "nonew": true,
18 | "nomen": false,
19 | "onevar": false,
20 | "plusplus": false,
21 | "regexp": false,
22 | "undef": true,
23 | "sub": true,
24 | "white": false,
25 | "eqeqeq": false,
26 | "latedef": true,
27 | "unused": "vars",
28 | "strict": false,
29 | "eqnull": true
30 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "marko-redux",
3 | "version": "1.0.0",
4 | "description": "Sample app: Marko + Redux",
5 | "main": "server.js",
6 | "scripts": {
7 | "start": "node server.js"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/marko-js-samples/marko-redux.git"
12 | },
13 | "author": "Patrick Steele-idem ",
14 | "license": "ISC",
15 | "bugs": {
16 | "url": "https://github.com/marko-js-samples/marko-redux/issues"
17 | },
18 | "homepage": "https://github.com/marko-js-samples/marko-redux",
19 | "dependencies": {
20 | "compression": "^1.6.2",
21 | "express": "^4.15.2",
22 | "lasso": "^2.11.5",
23 | "lasso-marko": "^2.3.0",
24 | "marko": "^4.0.0",
25 | "redux": "^3.6.0"
26 | },
27 | "devDependencies": {}
28 | }
29 |
--------------------------------------------------------------------------------
/routes/index/components/counter.marko:
--------------------------------------------------------------------------------
1 | /**
2 | * This is a presentation component that has no knowledge of Redux.
3 | * When an action needs to be performed it simply emits an event
4 | * that needs to be handled by a parent container component.
5 | * In this example, the `app` component will handle the `increment` and
6 | * `decrement` actions by directly interacting with the Redux store.
7 | */
8 | class {
9 | increment() {
10 | this.emit('increment');
11 | }
12 |
13 | decrement() {
14 | this.emit('decrement');
15 | }
16 |
17 | incrementIfOdd() {
18 | if (this.input.value % 2 !== 0) {
19 | this.increment();
20 | }
21 | }
22 |
23 | incrementAsync() {
24 | setTimeout(() => {
25 | this.increment();
26 | }, 1000);
27 | }
28 | }
29 |
30 |
31 | Current count: ${input.value}
32 |
33 |
34 |
35 |
38 |
41 |
42 |
--------------------------------------------------------------------------------
/routes/server-side-redux/components/counter.marko:
--------------------------------------------------------------------------------
1 | /**
2 | * This is a presentation component that has no knowledge of Redux.
3 | * When an action needs to be performed it simply emits an event
4 | * that needs to be handled by a parent container component.
5 | * In this example, the `app` component will handle the `increment` and
6 | * `decrement` actions by directly interacting with the Redux store.
7 | */
8 | class {
9 | increment() {
10 | this.emit('increment');
11 | }
12 |
13 | decrement() {
14 | this.emit('decrement');
15 | }
16 |
17 | incrementIfOdd() {
18 | if (this.input.value % 2 !== 0) {
19 | this.increment();
20 | }
21 | }
22 |
23 | incrementAsync() {
24 | setTimeout(() => {
25 | this.increment();
26 | }, 1000);
27 | }
28 | }
29 |
30 |
31 | Current count: ${input.value}
32 |
33 |
34 |
35 |
38 |
41 |
42 |
--------------------------------------------------------------------------------
/routes/server-side-redux/components/app.marko:
--------------------------------------------------------------------------------
1 | import store from '../store';
2 |
3 | class {
4 | onCreate(input, out) {
5 | // out.global is received from the $global object from route.js
6 | this.PRELOADED_STATE = out.global.PRELOADED_STATE;
7 |
8 | // create redux store on the server-side
9 | this.reduxStore = store.initialize(this.PRELOADED_STATE);
10 | }
11 |
12 | onMount() {
13 | // recreate redux store on the client-side
14 | this.reduxStore = store.initialize(this.PRELOADED_STATE);
15 | this.reduxStore.subscribe(() => {
16 | this.forceUpdate();
17 | });
18 | }
19 |
20 | dispatch(type) {
21 | this.reduxStore.dispatch({ type: type });
22 | }
23 | }
24 |
25 | $ var reduxState = {};
26 | /*
27 | * On the client-side, Marko first does a "lightweight re-render" of the UI
28 | * component tree before the `onMount` event is fired. The DOM is not actually
29 | * updated in this "lightweight re-render".
30 | * We check for the existence of `this.reduxStore` because it is first created in
31 | * the `onMount` event.
32 | */
33 | $ if(typeof component.reduxStore.getState === 'function') {
34 | reduxState = component.reduxStore.getState();
35 | }
36 |
37 |
41 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | require('marko/express');
2 | require('marko/node-require');
3 |
4 | var express = require('express');
5 | var compression = require('compression'); // Provides gzip compression for the HTTP response
6 |
7 | var isProduction = process.env.NODE_ENV === 'production';
8 |
9 | // Configure the RaptorJS Optimizer to control how JS/CSS/etc. is
10 | // delivered to the browser
11 | require('lasso').configure({
12 | plugins: [
13 | 'lasso-marko' // Allow Marko templates to be compiled and transported to the browser
14 | ],
15 | outputDir: __dirname + '/static', // Place all generated JS/CSS/etc. files into the "static" dir
16 | bundlingEnabled: isProduction, // Only enable bundling in production
17 | minify: isProduction, // Only minify JS and CSS code in production
18 | fingerprintsEnabled: isProduction, // Only add fingerprints to URLs in production
19 | });
20 |
21 |
22 | var app = express();
23 |
24 | var port = process.env.PORT || 8080;
25 |
26 | // Enable gzip compression for all HTTP responses
27 | app.use(compression());
28 |
29 | // Allow all of the generated files under "static" to be served up by Express
30 | app.use(require('lasso/middleware').serveStatic());
31 |
32 | // Map the "/" route to the home page
33 | app.get('/', require('./routes/index/route'));
34 | app.get('/server-side-redux/:value', require('./routes/server-side-redux/route'));
35 |
36 | app.listen(port, function(err) {
37 | if (err) {
38 | throw err;
39 | }
40 | console.log('Listening on port %d', port);
41 |
42 | // The browser-refresh module uses this event to know that the
43 | // process is ready to serve traffic after the restart
44 | if (process.send) {
45 | process.send('online');
46 | }
47 | });
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Marko + Redux
2 | ==================================
3 |
4 | This sample illustrates how to use Marko with Redux. Redux is a general-purpose application state container. This sample app illustrates how easy it is to wire up a Marko UI component with a Redux store.
5 |
6 | # Installation and running
7 |
8 | ```bash
9 | git clone https://github.com/marko-js-samples/marko-redux.git
10 | cd marko-redux
11 | npm install
12 | npm start
13 | ```
14 | # More details
15 |
16 | The partial code snippet below shows how a a Marko UI component can be connected to a Redux store using the `store.subscribe()` method and the Marko `forceUpdate()` method:
17 |
18 | ```jsx
19 | import store from '../store';
20 |
21 | class {
22 | onMount() {
23 | store.subscribe(() => {
24 | // Force this UI component to rerender:
25 | this.forceUpdate();
26 |
27 | // The UI component will be rerendered using the new
28 | // state returned by `store.getState()`
29 | //
30 | // The following is another option to force an update:
31 | // this.input = store.getState();
32 | });
33 | }
34 | }
35 |
36 |
37 |
38 |
39 | ```
40 |
41 | In the above example, the imported store module exports a Redux store created using the following code:
42 |
43 | ```js
44 | var redux = require('redux');
45 | var counter = require('./reducers');
46 |
47 | module.exports = redux.createStore(counter);
48 | ```
49 |
50 | # Server-side rendering
51 |
52 | With Marko, we can also send the initial state of your Redux store from the server.
53 |
54 | We can pass the initial state as a `global` variable, as shown in the following code:
55 |
56 | ```js
57 | module.exports = function(req, res) {
58 | var value = parseInt(req.params.value);
59 |
60 | res.marko(view, {
61 | $global: {
62 | PRELOADED_STATE: {
63 | value: value,
64 | },
65 | },
66 | });
67 | }
68 | ```
69 |
70 | When we go to`localhost:8080/server-side/redux/123`, the `123` is used as the initial state of our redux store and passed to the client.
71 |
72 | In our Marko component, we can use the initial state like so:
73 |
74 | ```jsx
75 | import store from '../store';
76 |
77 | class {
78 | onCreate(input, out) {
79 | // out.global is received from the $global object from route.js
80 | this.PRELOADED_STATE = out.global.PRELOADED_STATE;
81 |
82 | // create redux store on the server-side
83 | this.reduxStore = store.initialize(this.PRELOADED_STATE);
84 | }
85 |
86 | onMount() {
87 | // recreate redux store on the client-side
88 | this.reduxStore = store.initialize(this.PRELOADED_STATE);
89 | this.reduxStore.subscribe(() => {
90 | this.forceUpdate();
91 | });
92 | }
93 |
94 | dispatch(type) {
95 | this.reduxStore.dispatch({ type: type });
96 | }
97 | }
98 | ```
99 |
100 | In the above example, the imported store module is slightly different than the previous example. This module exports a Immediately-Invoked Function Expression (IFFE) which returns the Redux store. Using an IFFE allows us to create our Redux store with an initial state from the server, and share it with all other Marko components.
101 |
--------------------------------------------------------------------------------