├── .eslintrc ├── .gitignore ├── DOC ├── INTRO.md ├── STEP1.md ├── STEP10.md ├── STEP2.md ├── STEP3.md ├── STEP4.md ├── STEP5.md ├── STEP6.md ├── STEP7.md ├── STEP8.md └── STEP9.md ├── LICENSE ├── README.md ├── bower.json ├── dist ├── index.js └── main.js ├── gulpfile.js ├── package.json ├── public ├── index.html └── static ├── src └── js │ ├── .eslintrc │ ├── actions │ └── CommentActions.es6 │ ├── app.es6 │ ├── components │ ├── Comment.es6 │ ├── CommentSite.es6 │ └── MyComponent.es6 │ ├── main.es6 │ ├── mixins │ └── DBMixin.es6 │ └── stores │ └── CommentStore.es6 ├── webpack.base.config.js ├── webpack.config.js └── webpack.main.config.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true 5 | }, 6 | "rules": { 7 | "strict": [2, "global"], 8 | "no-shadow": 2, 9 | "no-shadow-restricted-names": 2, 10 | "no-unused-vars": [2, { 11 | "vars": "local", 12 | "args": "after-used" 13 | }], 14 | "no-use-before-define": 2, 15 | "comma-dangle": [2, "never"], 16 | "no-cond-assign": [2, "always"], 17 | "no-console": 1, 18 | "no-debugger": 1, 19 | "no-alert": 1, 20 | "no-constant-condition": 1, 21 | "no-dupe-keys": 2, 22 | "no-duplicate-case": 2, 23 | "no-empty": 2, 24 | "no-ex-assign": 2, 25 | "no-extra-boolean-cast": 0, 26 | "no-extra-semi": 2, 27 | "no-func-assign": 2, 28 | "no-inner-declarations": 2, 29 | "no-invalid-regexp": 2, 30 | "no-irregular-whitespace": 2, 31 | "no-obj-calls": 2, 32 | "no-reserved-keys": 2, 33 | "no-sparse-arrays": 2, 34 | "no-unreachable": 2, 35 | "use-isnan": 2, 36 | "block-scoped-var": 2, 37 | "consistent-return": 2, 38 | "curly": [2, "multi-line"], 39 | "default-case": 2, 40 | "dot-notation": [2, { 41 | "allowKeywords": true 42 | }], 43 | "eqeqeq": 2, 44 | "guard-for-in": 2, 45 | "no-caller": 2, 46 | "no-else-return": 2, 47 | "no-eq-null": 2, 48 | "no-eval": 2, 49 | "no-extend-native": 2, 50 | "no-extra-bind": 2, 51 | "no-fallthrough": 2, 52 | "no-floating-decimal": 2, 53 | "no-implied-eval": 2, 54 | "no-lone-blocks": 2, 55 | "no-loop-func": 2, 56 | "no-multi-str": 2, 57 | "no-native-reassign": 2, 58 | "no-new": 2, 59 | "no-new-func": 2, 60 | "no-new-wrappers": 2, 61 | "no-octal": 2, 62 | "no-octal-escape": 2, 63 | "no-param-reassign": 2, 64 | "no-proto": 2, 65 | "no-redeclare": 2, 66 | "no-return-assign": 2, 67 | "no-script-url": 2, 68 | "no-self-compare": 2, 69 | "no-sequences": 2, 70 | "no-throw-literal": 2, 71 | "no-with": 2, 72 | "radix": 2, 73 | "vars-on-top": 2, 74 | "wrap-iife": [2, "any"], 75 | "yoda": 2, 76 | "indent": [2, 2], 77 | "brace-style": [2, 78 | "1tbs", { 79 | "allowSingleLine": true 80 | }], 81 | "quotes": [ 82 | 2, "single", "avoid-escape" 83 | ], 84 | "camelcase": [2, { 85 | "properties": "never" 86 | }], 87 | "comma-spacing": [2, { 88 | "before": false, 89 | "after": true 90 | }], 91 | "comma-style": [2, "last"], 92 | "eol-last": 2, 93 | "func-names": 1, 94 | "key-spacing": [2, { 95 | "beforeColon": false, 96 | "afterColon": true 97 | }], 98 | "new-cap": [2, { 99 | "newIsCap": true 100 | }], 101 | "no-multiple-empty-lines": [2, { 102 | "max": 2 103 | }], 104 | "no-nested-ternary": 2, 105 | "no-new-object": 2, 106 | "no-spaced-func": 2, 107 | "no-trailing-spaces": 2, 108 | "no-wrap-func": 2, 109 | "no-underscore-dangle": 0, 110 | "one-var": [2, "never"], 111 | "padded-blocks": [2, "never"], 112 | "semi": [2, "always"], 113 | "semi-spacing": [2, { 114 | "before": false, 115 | "after": true 116 | }], 117 | "space-after-keywords": 2, 118 | "space-before-blocks": 2, 119 | "space-before-function-paren": [2, "never"], 120 | "space-infix-ops": 2, 121 | "space-return-throw-case": 2, 122 | "spaced-line-comment": 2, 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | db.json 3 | -------------------------------------------------------------------------------- /DOC/INTRO.md: -------------------------------------------------------------------------------- 1 | # Intro 2 | This tutorial is created to introduce some frontend libraries & frameworks. 3 | 4 | To avoid confusion, This tutorial is written simply and exclude optimization. 5 | 6 | if you want more information, look 'Related Links' placed at bottom of each tutorial. 7 | 8 | ## Table of Contents 9 | 1. [STEP1: Initialize node package](#step1-initialize-node-package) 10 | 2. [STEP2: Use another node package](#step2-use-another-node-package) 11 | 3. [STEP3: Make my package browser-executable by Webpack](#step3-make-my-package-browser-executable-by-webpack) 12 | 4. [STEP4: Use ES6 syntax by Babel](#step4-use-es6-syntax-by-babel) 13 | 5. [STEP5: Use React](#step5-use-react) 14 | 6. [STEP6: Use Style Guide by ESLint](#step6-use-style-guide-by-eslint) 15 | 7. [STEP7: Manage task by Gulp](#step7-manage-task-by-gulp) 16 | 8. [STEP8: Add Sourcemaps by Webpack](#step8-add-sourcemaps-by-webpack) 17 | 9. [STEP9: Create Simple app with Reflux & React](#step9-create-simple-app-with-reflux--react) 18 | 10. [STEP10: Make your app sync with REST API server with json-server & jquery](#step10-make-your-app-sync-with-rest-api-server-with-json-server--jquery) 19 | -------------------------------------------------------------------------------- /DOC/STEP1.md: -------------------------------------------------------------------------------- 1 | ## Step1: Initialize node package 2 | 3 | 1. Initailize node package. 4 | 5 | ```bash 6 | npm init # This command make package.json 7 | ``` 8 | 9 | package.json 10 | ```json 11 | { 12 | "name": "step-by-step-frontend", 13 | "version": "0.0.0", 14 | "description": "step by step learning about frontend", 15 | "main": "index.js", 16 | "author": "ironhee ", 17 | "license": "MIT" 18 | } 19 | ``` 20 | 21 | 3. Write your node module. 22 | 23 | index.js 24 | ```javascript 25 | 'use strict'; 26 | 27 | module.exports = function helloWorld() { 28 | console.log('hello world!'); 29 | }; 30 | ``` 31 | 32 | 4. load & run your module. 33 | 34 | test.js 35 | ```javascript 36 | 'use strict'; 37 | 38 | var helloWorld = require('./'); 39 | helloWorld(); 40 | ``` 41 | 42 | ```bash 43 | node test.js # hello world! 44 | ``` 45 | 46 | ### Related links 47 | 48 | + [strict mode ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode) 49 | + [npm init](https://docs.npmjs.com/cli/init) 50 | + [package.json](https://docs.npmjs.com/files/package.json) 51 | + [modules](https://nodejs.org/api/modules.html) 52 | -------------------------------------------------------------------------------- /DOC/STEP10.md: -------------------------------------------------------------------------------- 1 | ## STEP10: Make your app sync with REST API server with json-server & jquery 2 | 3 | 1. Install json-server globally by npm 4 | 5 | ```bash 6 | npm install -g json-server 7 | ``` 8 | 9 | 2. Create directory for json-server 10 | 11 | ```bash 12 | mkdir public 13 | ``` 14 | 15 | 3. Move demo/index.html to public/index.html 16 | 17 | ```bash 18 | mv demo/index.html public 19 | rm -rf demo 20 | ``` 21 | 22 | 4. Change content of public/index.html 23 | 24 | public/index.html 25 | ```html 26 | 27 | 28 | 29 | 30 | 31 | ``` 32 | 33 | 5. Make symbolic link of static files. 34 | 35 | ``` 36 | ln -s ../dist/ public/static 37 | ``` 38 | 39 | 6. create db.json 40 | 41 | db.json 42 | ```json 43 | {} 44 | ``` 45 | 46 | 7. Run json-server 47 | 48 | ```bash 49 | json-server db.json 50 | # {^_^} Hi! 51 | # 52 | # Loading database from db.json 53 | # 54 | # 55 | # You can now go to http://localhost:3000 56 | # 57 | # Enter s at any time to create a snapshot # of the db 58 | ``` 59 | 60 | 8. Open http://localhost:3000 in browser. and check your app is correctly operated. 61 | 62 | 9. Install jquery, url-join by npm 63 | 64 | ```bash 65 | npm install -S jquery url-join 66 | ``` 67 | 68 | 10. Make CommentStore use Ajax Request. 69 | 70 | src/js/mixins/DBMixin.es6 71 | ```javascript 72 | import underscoreDB from 'underscore-db'; 73 | import _ from 'underscore'; 74 | import $ from 'jquery'; 75 | import urlJoin from 'url-join'; 76 | import { Promise } from 'q'; 77 | 78 | _.mixin(underscoreDB); 79 | 80 | function ajaxRequest(options) { 81 | return new Promise((resolve, reject) => { 82 | $.ajax(options) 83 | .then(resolve) 84 | .fail(reject); 85 | }); 86 | } 87 | 88 | export default function DBMixin(type) { 89 | let result = { 90 | db: [] 91 | }; 92 | let methods = _(result.db); 93 | _.extend(result, methods); 94 | _.extend(result, { 95 | insert(attributes) { 96 | return ajaxRequest({ 97 | type: 'POST', 98 | url: urlJoin(type), 99 | data: attributes 100 | }) 101 | .then(response => { 102 | return response; 103 | }) 104 | .then(response => methods.insert(response)); 105 | }, 106 | removeById(id) { 107 | return ajaxRequest({ 108 | type: 'DELETE', 109 | url: urlJoin(type, id) 110 | }) 111 | .then(() => methods.removeById(id)); 112 | } 113 | }); 114 | 115 | return result; 116 | } 117 | ``` 118 | 119 | src/js/stores/CommentStore.es6 120 | ```javascript 121 | import Reflux from 'reflux'; 122 | import CommentActions from 'actions/CommentActions'; 123 | import DBMixin from 'mixins/DBMixin'; 124 | import { Promise } from 'q'; 125 | 126 | export default Reflux.createStore({ 127 | 128 | mixins: [new DBMixin('comments')], 129 | 130 | listenables: [CommentActions], 131 | 132 | onCreateComment(content) { 133 | CommentActions.createComment.promise( 134 | new Promise((resolve, reject) => { 135 | this.insert({ 136 | content, 137 | updatedAt: new Date().getTime() 138 | }) 139 | .then(comment => resolve(comment)) 140 | .then(() => this.trigger()) 141 | .catch(reject); 142 | }) 143 | ); 144 | }, 145 | 146 | onRemoveComment(commentID) { 147 | CommentActions.removeComment.promise( 148 | new Promise((resolve, reject) => { 149 | this.removeById(commentID) 150 | .then(comment => resolve(comment)) 151 | .then(() => this.trigger()) 152 | .catch(reject); 153 | }) 154 | ); 155 | } 156 | 157 | }); 158 | ``` 159 | 160 | Warning: comment.updatedAt field's type is change. 161 | 162 | 11. Apply comment.updatedAt field's type change. 163 | 164 | src/js/components/Comment.es6 165 | ```javascript 166 | import React from 'react'; 167 | import CommentActions from 'actions/CommentActions'; 168 | 169 | export default React.createClass({ 170 | 171 | propTypes: { 172 | comment: React.PropTypes.shape({ 173 | content: React.PropTypes.string.isRequired, 174 | updatedAt: React.PropTypes.number.isRequired 175 | }).isRequired 176 | }, 177 | 178 | onRemove() { 179 | CommentActions.removeComment(this.props.comment.id) 180 | .then(() => { 181 | alert('removed!'); 182 | }); 183 | return false; 184 | }, 185 | 186 | render() { 187 | return ( 188 |
189 | { this.props.comment.content } - 190 | { new Date(this.props.comment.updatedAt).toDateString() } 191 | remove 192 |
193 | ); 194 | } 195 | 196 | }); 197 | ``` 198 | 199 | 12. Open http://localhost:3000 in browser. and check your app make ajax request correctly. 200 | 201 | 14. add fetchComments action to CommentActions 202 | 203 | src/js/actions/CommentActions.es6 204 | ```javascript 205 | import Reflux from 'reflux'; 206 | 207 | export default Reflux.createActions({ 208 | 209 | fetchComments: { 210 | asyncResult: true 211 | }, 212 | 213 | createComment: { 214 | asyncResult: true 215 | }, 216 | 217 | removeComment: { 218 | asyncResult: true 219 | } 220 | 221 | }); 222 | ``` 223 | 224 | 15. make CommentSite trigger fetchComment action after rendered. (componentDidMount) 225 | 226 | src/js/components/CommentSite.es6 227 | ```javascript 228 | import React from 'react'; 229 | import Reflux from 'reflux'; 230 | import _ from 'underscore'; 231 | import Comment from 'components/Comment'; 232 | import CommentStore from 'stores/CommentStore'; 233 | import CommentActions from 'actions/CommentActions'; 234 | 235 | function getStoreState() { 236 | return { 237 | comments: CommentStore.value() 238 | }; 239 | } 240 | 241 | export default React.createClass({ 242 | 243 | mixins: [ 244 | Reflux.listenTo(CommentStore, 'onStoreChange') 245 | ], 246 | 247 | getInitialState() { 248 | return getStoreState(); 249 | }, 250 | 251 | componentDidMount() { 252 | CommentActions.fetchComments(); 253 | }, 254 | 255 | onStoreChange() { 256 | this.setState(getStoreState()); 257 | }, 258 | 259 | onCreateComment() { 260 | let content = React.findDOMNode(this.refs.newComment).value; 261 | CommentActions.createComment(content) 262 | .then(() => { 263 | alert('created!'); 264 | }); 265 | return false; 266 | }, 267 | 268 | render() { 269 | return ( 270 |
271 |

Comments

272 | { _.map(this.state.comments, comment => ( 273 | 274 | )) } 275 |
276 | 277 | 278 |
279 |
280 | ); 281 | } 282 | 283 | }); 284 | ``` 285 | 286 | 16. implement fetch method 287 | 288 | src/js/mixins/DBMixin.es6 289 | ```javascript 290 | import underscoreDB from 'underscore-db'; 291 | import _ from 'underscore'; 292 | import $ from 'jquery'; 293 | import urlJoin from 'url-join'; 294 | import { Promise } from 'q'; 295 | 296 | _.mixin(underscoreDB); 297 | 298 | function ajaxRequest(options) { 299 | return new Promise((resolve, reject) => { 300 | $.ajax(options) 301 | .then(resolve) 302 | .fail(reject); 303 | }); 304 | } 305 | 306 | export default function DBMixin(type) { 307 | let result = { 308 | db: [] 309 | }; 310 | let methods = _(result.db); 311 | _.extend(result, methods); 312 | _.extend(result, { 313 | insert(attributes) { 314 | return ajaxRequest({ 315 | type: 'POST', 316 | url: urlJoin(type), 317 | data: attributes 318 | }) 319 | .then(response => { 320 | return response; 321 | }) 322 | .then(response => methods.insert(response)); 323 | }, 324 | removeById(id) { 325 | return ajaxRequest({ 326 | type: 'DELETE', 327 | url: urlJoin(type, id) 328 | }) 329 | .then(() => methods.removeById(id)); 330 | }, 331 | fetch(id) { 332 | return ajaxRequest({ 333 | type: 'GET', 334 | url: urlJoin(type, id) 335 | }) 336 | .then(response => _.isArray(response) ? 337 | _.map(response, _response => methods.insert(_response)) : 338 | methods.insert(response) 339 | ); 340 | } 341 | }); 342 | 343 | return result; 344 | } 345 | ``` 346 | 347 | src/js/stores/CommentStore.es6 348 | ```javascript 349 | import Reflux from 'reflux'; 350 | import CommentActions from 'actions/CommentActions'; 351 | import DBMixin from 'mixins/DBMixin'; 352 | import { Promise } from 'q'; 353 | 354 | export default Reflux.createStore({ 355 | 356 | mixins: [new DBMixin('comments')], 357 | 358 | listenables: [CommentActions], 359 | 360 | onFetchComments() { 361 | CommentActions.fetchComments.promise( 362 | new Promise((resolve, reject) => { 363 | this.fetch() 364 | .then(comments => resolve(comments)) 365 | .then(() => this.trigger()) 366 | .catch(reject); 367 | }) 368 | ); 369 | }, 370 | 371 | onCreateComment(content) { 372 | CommentActions.createComment.promise( 373 | new Promise((resolve, reject) => { 374 | this.insert({ 375 | content, 376 | updatedAt: new Date().getTime() 377 | }) 378 | .then(comment => resolve(comment)) 379 | .then(() => this.trigger()) 380 | .catch(reject); 381 | }) 382 | ); 383 | }, 384 | 385 | onRemoveComment(commentID) { 386 | CommentActions.removeComment.promise( 387 | new Promise((resolve, reject) => { 388 | this.removeById(commentID) 389 | .then(comment => resolve(comment)) 390 | .then(() => this.trigger()) 391 | .catch(reject); 392 | }) 393 | ); 394 | } 395 | 396 | }); 397 | ``` 398 | 399 | 17. Open http://localhost:3000 in browser. and check your app make get request after initial rendering and your comments is correctly rendered. 400 | 401 | 18. add db.json to .gitignore 402 | 403 | .gitignore 404 | ``` 405 | node_modules 406 | db.json 407 | ``` 408 | 409 | ### Related links 410 | 411 | + [url-join](https://github.com/jfromaniello/url-join) 412 | + [json-server](https://github.com/typicode/json-server) 413 | + [jquery](https://github.com/jquery/jquery) 414 | + [ajax](https://developer.mozilla.org/en-US/docs/AJAX) 415 | + [what-exactly-is-restful-programming](http://stackoverflow.com/questions/671118/what-exactly-is-restful-programming) 416 | + [RFC2616 - Method](http://tools.ietf.org/html/rfc2616#section-9) 417 | + [jsonapi](http://jsonapi.org/) 418 | -------------------------------------------------------------------------------- /DOC/STEP2.md: -------------------------------------------------------------------------------- 1 | ## Step2: Use another node package 2 | 3 | 1. Install another node package. 4 | 5 | ```bash 6 | npm install --save underscore 7 | ``` 8 | 9 | package.json 10 | ```json 11 | { 12 | "name": "step-by-step-frontend", 13 | "version": "0.0.0", 14 | "description": "step by step learning about frontend", 15 | "main": "index.js", 16 | "author": "ironhee ", 17 | "license": "MIT", 18 | "dependencies": { 19 | "underscore": "^1.8.3" 20 | } 21 | } 22 | 23 | ``` 24 | 25 | 3. update your node module. 26 | 27 | index.js 28 | ```javascript 29 | 'use strict'; 30 | 31 | var _ = require('underscore'); 32 | 33 | module.exports = function helloWorld() { 34 | _.times(10, function (index) { 35 | console.log('[' + index + '] hello world!'); 36 | }); 37 | }; 38 | ``` 39 | 40 | 4. load & run your module. 41 | 42 | test.js 43 | ```javascript 44 | 'use strict'; 45 | 46 | var helloWorld = require('./'); 47 | helloWorld(); 48 | ``` 49 | 50 | ```bash 51 | node test.js # [0] hello world! ... 52 | ``` 53 | 54 | 5. create .gitignore 55 | 56 | .gitignore 57 | ``` 58 | node_modules 59 | ``` 60 | 61 | ### Related links 62 | 63 | + [require](https://nodejs.org/api/modules.html) 64 | + [npm install](https://docs.npmjs.com/cli/install) 65 | + [dependencies](https://docs.npmjs.com/files/package.json#dependencies) 66 | -------------------------------------------------------------------------------- /DOC/STEP3.md: -------------------------------------------------------------------------------- 1 | ## Step3: Make my package browser-executable by Webpack 2 | 3 | 1. Install & Initailize bower package. 4 | 5 | ```bash 6 | npm install -g bower 7 | bower init # This command make bower.json 8 | ``` 9 | 10 | bower.json 11 | ```json 12 | { 13 | "name": "step-by-step-frontend", 14 | "version": "0.0.0", 15 | "description": "step by step learning about frontend", 16 | "main": "dist/index.js", 17 | "authors": [ 18 | "ironhee " 19 | ], 20 | "license": "MIT" 21 | } 22 | ``` 23 | 24 | 2. Install webpack 25 | 26 | ```bash 27 | npm install -g webpack 28 | ``` 29 | 30 | 3. create webpack config 31 | 32 | webpack.config.js 33 | ```javascript 34 | 'use strict'; 35 | 36 | var _ = require('underscore'); 37 | var pkg = require('./package.json'); 38 | 39 | module.exports = { 40 | entry: { 41 | 'index': './index.js' 42 | }, 43 | output: { 44 | path: 'dist/', 45 | filename: '[name].js', 46 | library: 'MyLib', 47 | libraryTarget: 'umd' 48 | } 49 | }; 50 | ``` 51 | 52 | 4. build source code by webpack 53 | 54 | ```bash 55 | webpack # use webpack.config.js by default 56 | ``` 57 | 58 | if you want to rebuild on file change, use --watch option 59 | ```bash 60 | webpack --watch # ctrl-c to exit 61 | ``` 62 | 63 | 5. load & run your module. 64 | 65 | test.html 66 | ```html 67 | 68 | 69 | 70 | 71 | 72 | 73 | ``` 74 | 75 | 6. change package.json 'main' property 76 | 77 | package.json 78 | ```json 79 | { 80 | "name": "step-by-step-frontend", 81 | "version": "0.0.0", 82 | "description": "step by step learning about frontend", 83 | "main": "dist/index.js", 84 | "author": "ironhee ", 85 | "license": "MIT", 86 | "dependencies": { 87 | "underscore": "^1.8.3" 88 | } 89 | } 90 | ``` 91 | 92 | 7. load & run your node module. 93 | 94 | test.js 95 | ```javascript 96 | 'use strict'; 97 | 98 | var helloWorld = require('./'); 99 | helloWorld(); 100 | ``` 101 | 102 | ```bash 103 | node test.js # [0] hello world! ... 104 | ``` 105 | 106 | ### Related links 107 | 108 | + [bower](https://github.com/bower/bower) 109 | + [webpack](https://github.com/webpack/webpack) 110 | + [webpack config](http://webpack.github.io/docs/configuration.html) 111 | -------------------------------------------------------------------------------- /DOC/STEP4.md: -------------------------------------------------------------------------------- 1 | ## Step4: Use ES6 syntax by Babel 2 | 3 | 1. Install babel-loader by npm 4 | 5 | ```bash 6 | npm install --save-dev babel-loader 7 | ``` 8 | 9 | 2. rename index.js and change content 10 | 11 | ```bash 12 | mv index.js index.es6 13 | ``` 14 | 15 | index.es6 16 | ```javascript 17 | import _ from 'underscore'; 18 | 19 | export default function helloWorld() { 20 | _.times(10, (index) => { 21 | console.log(`[${index}] hello world!`); 22 | }); 23 | } 24 | ``` 25 | 26 | 3. change webpack config 27 | 28 | webpack.config.js 29 | ```javascript 30 | 'use strict'; 31 | 32 | var _ = require('underscore'); 33 | var pkg = require('./package.json'); 34 | 35 | module.exports = { 36 | entry: { 37 | 'index': './index.es6' 38 | }, 39 | output: { 40 | path: 'dist/', 41 | filename: '[name].js', 42 | library: 'MyLib', 43 | libraryTarget: 'umd' 44 | }, 45 | module: { 46 | loaders: [ 47 | { test: /\.es6$/, loader: 'babel-loader' } 48 | ] 49 | } 50 | }; 51 | ``` 52 | 53 | 4. build source code by webpack 54 | 55 | ```bash 56 | webpack 57 | ``` 58 | 59 | 5. test browser-side and node-side 60 | 61 | test.html 62 | ```html 63 | 64 | 65 | 66 | 67 | 68 | 69 | ``` 70 | 71 | ```bash 72 | node test.js # [0] hello world! ... 73 | ``` 74 | 75 | ### Related links 76 | 77 | + [es6features](https://github.com/lukehoban/es6features) 78 | + [babel](https://github.com/babel/babel) 79 | + [webpack loader](http://webpack.github.io/docs/using-loaders.html) 80 | -------------------------------------------------------------------------------- /DOC/STEP5.md: -------------------------------------------------------------------------------- 1 | ## Step5: Use React 2 | 3 | 1. Install React by npm 4 | 5 | ```bash 6 | npm install --save react 7 | ``` 8 | 9 | 2. make some directories 10 | 11 | ```bash 12 | mkdir -p src/js/components 13 | ``` 14 | 15 | 3. create modules and rendering script 16 | 17 | src/js/components/MyComponent.es6 18 | ```javascript 19 | import React from 'react'; 20 | 21 | export default React.createClass({ 22 | 23 | render() { 24 | return ( 25 |
26 |

Hello world!

27 |
28 | ); 29 | } 30 | 31 | }); 32 | ``` 33 | 34 | src/js/app.es6 35 | ```javascript 36 | import MyComponent from './components/MyComponent'; 37 | 38 | export default { 39 | MyComponent 40 | }; 41 | ``` 42 | 43 | src/js/main.es6 44 | ```javascript 45 | import React from 'react'; 46 | import { MyComponent } from './app'; 47 | 48 | React.render(, document.body); 49 | ``` 50 | 51 | 4. remove old files and change webpack config 52 | 53 | ```bash 54 | rm -rf test.js test.html index.es6 dist/index.js 55 | ``` 56 | 57 | 5. separate webpack config 58 | 59 | webpack.base.config.js 60 | ```javascript 61 | 'use strict'; 62 | 63 | module.exports = { 64 | resolve: { 65 | extensions: ['', '.js', '.es6'] 66 | }, 67 | module: { 68 | loaders: [ 69 | { test: /\.es6$/, loader: 'babel-loader' } 70 | ] 71 | } 72 | }; 73 | ``` 74 | 75 | webpack.config.js 76 | ```javascript 77 | 'use strict'; 78 | 79 | var _ = require('underscore'); 80 | var baseConfig = require('./webpack.base.config'); 81 | 82 | module.exports = _.extend({}, baseConfig, { 83 | entry: { 84 | 'app': './src/js/app.es6' 85 | }, 86 | output: { 87 | path: 'dist/', 88 | filename: 'index.js', 89 | library: 'MyLib', 90 | libraryTarget: 'umd' 91 | } 92 | }); 93 | ``` 94 | 95 | webpack.main.config.js 96 | ```javascript 97 | 'use strict'; 98 | 99 | var _ = require('underscore'); 100 | var baseConfig = require('./webpack.base.config'); 101 | 102 | module.exports = _.extend({}, baseConfig, { 103 | entry: { 104 | 'main': './src/js/main.es6' 105 | }, 106 | output: { 107 | path: 'dist/', 108 | filename: 'main.js', 109 | libraryTarget: 'umd' 110 | } 111 | }); 112 | ``` 113 | 114 | 6. build by webpack 115 | 116 | ```bash 117 | webpack # build library code 118 | webpack --config webpack.main.config # build rendering code 119 | ``` 120 | 121 | 7. check in node and browser 122 | 123 | ```bash 124 | node -e 'console.log(require("./"))' 125 | # { MyComponent: { [Function] displayName: 'MyComponent' } } 126 | ``` 127 | 128 | demo/index.html 129 | ```html 130 | 131 | 132 | 133 | 134 | 135 | 136 | ``` 137 | 138 | 8. use main.js (rendering logic) in browser 139 | 140 | demo/index.html 141 | ```html 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | ``` 150 | 151 | 152 | ### Related links 153 | 154 | + [react](https://github.com/facebook/react) 155 | -------------------------------------------------------------------------------- /DOC/STEP6.md: -------------------------------------------------------------------------------- 1 | ## Step6: Use Style Guide by ESLint 2 | 3 | 1. Install eslint and plugins by npm 4 | 5 | ```bash 6 | npm install --save-dev eslint babel-eslint eslint-plugin-react 7 | ``` 8 | 9 | 2. make eslint configs 10 | 11 | .eslintrc 12 | ```json 13 | { 14 | "env": { 15 | "browser": true, 16 | "node": true 17 | }, 18 | "rules": { 19 | "strict": [2, "global"], 20 | "no-shadow": 2, 21 | "no-shadow-restricted-names": 2, 22 | "no-unused-vars": [2, { 23 | "vars": "local", 24 | "args": "after-used" 25 | }], 26 | "no-use-before-define": 2, 27 | "comma-dangle": [2, "never"], 28 | "no-cond-assign": [2, "always"], 29 | "no-console": 1, 30 | "no-debugger": 1, 31 | "no-alert": 1, 32 | "no-constant-condition": 1, 33 | "no-dupe-keys": 2, 34 | "no-duplicate-case": 2, 35 | "no-empty": 2, 36 | "no-ex-assign": 2, 37 | "no-extra-boolean-cast": 0, 38 | "no-extra-semi": 2, 39 | "no-func-assign": 2, 40 | "no-inner-declarations": 2, 41 | "no-invalid-regexp": 2, 42 | "no-irregular-whitespace": 2, 43 | "no-obj-calls": 2, 44 | "no-reserved-keys": 2, 45 | "no-sparse-arrays": 2, 46 | "no-unreachable": 2, 47 | "use-isnan": 2, 48 | "block-scoped-var": 2, 49 | "consistent-return": 2, 50 | "curly": [2, "multi-line"], 51 | "default-case": 2, 52 | "dot-notation": [2, { 53 | "allowKeywords": true 54 | }], 55 | "eqeqeq": 2, 56 | "guard-for-in": 2, 57 | "no-caller": 2, 58 | "no-else-return": 2, 59 | "no-eq-null": 2, 60 | "no-eval": 2, 61 | "no-extend-native": 2, 62 | "no-extra-bind": 2, 63 | "no-fallthrough": 2, 64 | "no-floating-decimal": 2, 65 | "no-implied-eval": 2, 66 | "no-lone-blocks": 2, 67 | "no-loop-func": 2, 68 | "no-multi-str": 2, 69 | "no-native-reassign": 2, 70 | "no-new": 2, 71 | "no-new-func": 2, 72 | "no-new-wrappers": 2, 73 | "no-octal": 2, 74 | "no-octal-escape": 2, 75 | "no-param-reassign": 2, 76 | "no-proto": 2, 77 | "no-redeclare": 2, 78 | "no-return-assign": 2, 79 | "no-script-url": 2, 80 | "no-self-compare": 2, 81 | "no-sequences": 2, 82 | "no-throw-literal": 2, 83 | "no-with": 2, 84 | "radix": 2, 85 | "vars-on-top": 2, 86 | "wrap-iife": [2, "any"], 87 | "yoda": 2, 88 | "indent": [2, 2], 89 | "brace-style": [2, 90 | "1tbs", { 91 | "allowSingleLine": true 92 | }], 93 | "quotes": [ 94 | 2, "single", "avoid-escape" 95 | ], 96 | "camelcase": [2, { 97 | "properties": "never" 98 | }], 99 | "comma-spacing": [2, { 100 | "before": false, 101 | "after": true 102 | }], 103 | "comma-style": [2, "last"], 104 | "eol-last": 2, 105 | "func-names": 1, 106 | "key-spacing": [2, { 107 | "beforeColon": false, 108 | "afterColon": true 109 | }], 110 | "new-cap": [2, { 111 | "newIsCap": true 112 | }], 113 | "no-multiple-empty-lines": [2, { 114 | "max": 2 115 | }], 116 | "no-nested-ternary": 2, 117 | "no-new-object": 2, 118 | "no-spaced-func": 2, 119 | "no-trailing-spaces": 2, 120 | "no-wrap-func": 2, 121 | "no-underscore-dangle": 0, 122 | "one-var": [2, "never"], 123 | "padded-blocks": [2, "never"], 124 | "semi": [2, "always"], 125 | "semi-spacing": [2, { 126 | "before": false, 127 | "after": true 128 | }], 129 | "space-after-keywords": 2, 130 | "space-before-blocks": 2, 131 | "space-before-function-paren": [2, "never"], 132 | "space-infix-ops": 2, 133 | "space-return-throw-case": 2, 134 | "spaced-line-comment": 2, 135 | } 136 | } 137 | ``` 138 | 139 | src/js/.estlinrc 140 | ```json 141 | { 142 | "parser": "babel-eslint", 143 | "plugins": [ 144 | "react" 145 | ], 146 | "ecmaFeatures": { 147 | "arrowFunctions": true, 148 | "blockBindings": true, 149 | "classes": true, 150 | "defaultParams": true, 151 | "destructuring": true, 152 | "forOf": true, 153 | "generators": false, 154 | "modules": true, 155 | "objectLiteralComputedProperties": true, 156 | "objectLiteralDuplicateProperties": false, 157 | "objectLiteralShorthandMethods": true, 158 | "objectLiteralShorthandProperties": true, 159 | "spread": true, 160 | "superInFunctions": true, 161 | "templateStrings": true, 162 | "jsx": true 163 | }, 164 | "rules": { 165 | "strict": [2, "never"], 166 | "no-var": 2, 167 | "react/display-name": 0, 168 | "react/jsx-boolean-value": 2, 169 | "react/jsx-quotes": [2, "double"], 170 | "react/jsx-no-undef": 2, 171 | "react/jsx-sort-props": 0, 172 | "react/jsx-sort-prop-types": 0, 173 | "react/jsx-uses-react": 2, 174 | "react/jsx-uses-vars": 2, 175 | "react/no-did-mount-set-state": [2, "allow-in-func"], 176 | "react/no-did-update-set-state": 2, 177 | "react/no-multi-comp": 2, 178 | "react/no-unknown-property": 2, 179 | "react/prop-types": 2, 180 | "react/react-in-jsx-scope": 2, 181 | "react/self-closing-comp": 2, 182 | "react/wrap-multilines": 2, 183 | "react/sort-comp": [2, { 184 | "order": [ 185 | "displayName", 186 | "mixins", 187 | "statics", 188 | "propTypes", 189 | "getDefaultProps", 190 | "getInitialState", 191 | "componentWillMount", 192 | "componentDidMount", 193 | "componentWillReceiveProps", 194 | "shouldComponentUpdate", 195 | "componentWillUpdate", 196 | "componentWillUnmount", 197 | "/^on.+$/", 198 | "/^get.+$/", 199 | "/^render.+$/", 200 | "render" 201 | ] 202 | }] 203 | } 204 | } 205 | ``` 206 | 207 | 3. install editor plugin 208 | - sublime text: Install SublimeLinter & SublimeLinter-eslint 209 | - atom: Install linter & linter-eslint 210 | 211 | ### Related links 212 | 213 | + [airbnb javascript style guide](https://github.com/airbnb/javascript) 214 | + [eslint](http://eslint.org/) 215 | + [eslint config](http://eslint.org/docs/user-guide/configuring.html) 216 | + [atom](https://atom.io/) 217 | + [sublimt text](http://www.sublimetext.com/) 218 | -------------------------------------------------------------------------------- /DOC/STEP7.md: -------------------------------------------------------------------------------- 1 | ## Step7: Manage task by Gulp 2 | 3 | 1. Install gulp by npm 4 | 5 | ```bash 6 | npm install -g gulp 7 | npm install --save-dev gulp 8 | ``` 9 | 10 | 2. Install webpack and plugin by npm 11 | 12 | ```bash 13 | npm install --save-dev webpack 14 | npm install --save-dev webpack-gulp-logger 15 | ``` 16 | 17 | 3. create gulp config file (gulpfile.js) 18 | 19 | gulpfile.js 20 | ```javascript 21 | 'use strict'; 22 | 23 | var gulp = require('gulp'); 24 | var webpack = require('webpack'); 25 | var webpackLogger = require('webpack-gulp-logger'); 26 | var libWebpackConfig = require('./webpack.config'); 27 | var mainWebpackConfig = require('./webpack.main.config'); 28 | 29 | gulp.task('default', [ 30 | 'watch' 31 | ]); 32 | 33 | gulp.task('watch', [ 34 | 'watch-lib', 35 | 'watch-main' 36 | ]); 37 | 38 | gulp.task('build', [ 39 | 'build-lib', 40 | 'build-main' 41 | ]); 42 | 43 | gulp.task('watch-lib', function() { 44 | webpack(libWebpackConfig).watch({}, webpackLogger()); 45 | }); 46 | 47 | gulp.task('watch-main', function() { 48 | webpack(mainWebpackConfig).watch({}, webpackLogger()); 49 | }); 50 | 51 | gulp.task('build-lib', function(callback) { 52 | webpack(libWebpackConfig).run(webpackLogger(callback)); 53 | }); 54 | 55 | gulp.task('build-main', function(callback) { 56 | webpack(mainWebpackConfig).run(webpackLogger(callback)); 57 | }); 58 | ``` 59 | 60 | 4. run gulp task 61 | 62 | for build 63 | ```bash 64 | gulp build 65 | ``` 66 | 67 | for watch 68 | ```bash 69 | gulp watch 70 | ``` 71 | 72 | ### Related links 73 | 74 | + [gulp](https://github.com/gulpjs/gulp) 75 | + [webpack api](http://webpack.github.io/docs/node.js-api.html) 76 | -------------------------------------------------------------------------------- /DOC/STEP8.md: -------------------------------------------------------------------------------- 1 | ## Step8: Add Sourcemaps by Webpack 2 | 3 | 1. set devtool property in webpack config 4 | 5 | webpack.base.config.js 6 | ```javascript 7 | 'use strict'; 8 | 9 | module.exports = { 10 | devtool: 'eval-source-map', 11 | resolve: { 12 | extensions: ['', '.js', '.es6'] 13 | }, 14 | module: { 15 | loaders: [ 16 | { test: /\.es6$/, loader: 'babel-loader' } 17 | ] 18 | } 19 | }; 20 | ``` 21 | 22 | 2. build by gulp 23 | 24 | ```bash 25 | gulp build 26 | ``` 27 | 28 | now you can distinguish source code. 29 | 30 | 31 | ### Related links 32 | 33 | + [sourcemap](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/?redirect_from_locale=ko) 34 | + [devtool option](http://webpack.github.io/docs/configuration.html#devtool) 35 | -------------------------------------------------------------------------------- /DOC/STEP9.md: -------------------------------------------------------------------------------- 1 | ## Step9: Create Simple app with Reflux & React 2 | 3 | 1. add resolve.modulesDirectories option to webpack config for convenience 4 | 5 | webpack.base.config 6 | ```javascript 7 | 'use strict'; 8 | 9 | module.exports = { 10 | devtool: 'eval-source-map', 11 | resolve: { 12 | modulesDirectories: ['src/js/', 'node_modules'], 13 | extensions: ['', '.js', '.es6'] 14 | }, 15 | module: { 16 | loaders: [ 17 | { test: /\.es6$/, loader: 'babel-loader' } 18 | ] 19 | } 20 | }; 21 | ``` 22 | 23 | 2. create Commment.es6 and CommentSite.es6 24 | 25 | src/js/components/Comment.es6 26 | ```javascript 27 | import React from 'react'; 28 | 29 | export default React.createClass({ 30 | 31 | propTypes: { 32 | comment: React.PropTypes.shape({ 33 | content: React.PropTypes.string.isRequired, 34 | updatedAt: React.PropTypes.object.isRequired 35 | }).isRequired 36 | }, 37 | 38 | render() { 39 | return ( 40 |
41 | { this.props.comment.content } - 42 | { this.props.comment.updatedAt.toDateString() } 43 | remove 44 |
45 | ); 46 | } 47 | 48 | }); 49 | ``` 50 | 51 | src/js/components/CommentSite.es6 52 | ```javascript 53 | import React from 'react'; 54 | import _ from 'underscore'; 55 | import Comment from 'components/Comment'; 56 | 57 | export default React.createClass({ 58 | 59 | getInitialState() { 60 | return { 61 | comments: [{ 62 | id: 1, 63 | content: 'this is comment1!', 64 | updatedAt: new Date(Date.now()) 65 | }, { 66 | id: 2, 67 | content: 'this is comment2!', 68 | updatedAt: new Date(Date.now()) 69 | }] 70 | }; 71 | }, 72 | 73 | render() { 74 | return ( 75 |
76 |

Comments

77 | { _.map(this.state.comments, comment => ( 78 | 79 | )) } 80 |
81 | 82 | 83 |
84 |
85 | ); 86 | } 87 | 88 | }); 89 | ``` 90 | 91 | 3. add CommentSite to app.es6 92 | 93 | src/js/app.es6 94 | ```javascript 95 | import MyComponent from 'components/MyComponent'; 96 | import CommentSite from 'components/CommentSite'; 97 | 98 | export default { 99 | MyComponent, 100 | CommentSite 101 | }; 102 | ``` 103 | 104 | 4. change main.es6 105 | 106 | main.es6 107 | ```javascript 108 | import React from 'react'; 109 | import { CommentSite } from 'app'; 110 | 111 | React.render(, document.body); 112 | ``` 113 | 114 | 5. open demo.index.html in browser and check components are correctly rendered 115 | 116 | 6. install reflux, q, underscore-db by npm 117 | 118 | ```bash 119 | npm install --save reflux q@~1.0 underscore-db 120 | ``` 121 | 122 | 7. add node.fs option to webpack config 123 | 124 | webpack.base.config 125 | ```javascript 126 | 'use strict'; 127 | 128 | module.exports = { 129 | devtool: 'eval-source-map', 130 | node: { 131 | fs: 'empty' 132 | }, 133 | resolve: { 134 | modulesDirectories: ['src/js/', 'node_modules'], 135 | extensions: ['', '.js', '.es6'] 136 | }, 137 | module: { 138 | loaders: [ 139 | { test: /\.es6$/, loader: 'babel-loader' } 140 | ] 141 | } 142 | }; 143 | ``` 144 | 145 | 8. define comment actions 146 | 147 | src/js/actions/CommentActions.es6 148 | ```javascript 149 | import Reflux from 'reflux'; 150 | 151 | export default Reflux.createActions({ 152 | 153 | createComment: { 154 | asyncResult: true 155 | }, 156 | 157 | removeComment: { 158 | asyncResult: true 159 | } 160 | 161 | }); 162 | ``` 163 | 164 | 9. set reflux promise factory to Q.Promise 165 | 166 | src/js/app.es6 167 | ```javascript 168 | import Reflux from 'reflux'; 169 | import Q from 'q'; 170 | Reflux.setPromiseFactory(Q.Promise); 171 | 172 | import MyComponent from 'components/MyComponent'; 173 | import CommentSite from 'components/CommentSite'; 174 | 175 | export default { 176 | MyComponent, 177 | CommentSite 178 | }; 179 | ``` 180 | 181 | 10. create comment store 182 | 183 | src/js/mixins/DBMixin.es6 184 | ```javascript 185 | import underscoreDB from 'underscore-db'; 186 | import _ from 'underscore'; 187 | 188 | _.mixin(underscoreDB); 189 | 190 | export default function DBMixin() { 191 | let result = { 192 | db: [] 193 | }; 194 | 195 | _.extend(result, _(result.db)); 196 | return result; 197 | } 198 | ``` 199 | 200 | src/js/stores/CommentStore.es6 201 | ```javascript 202 | import Reflux from 'reflux'; 203 | import CommentActions from 'actions/CommentActions'; 204 | import DBMixin from 'mixins/DBMixin'; 205 | import { Promise } from 'q'; 206 | 207 | export default Reflux.createStore({ 208 | 209 | mixins: [new DBMixin()], 210 | 211 | listenables: [CommentActions], 212 | 213 | onCreateComment(content) { 214 | CommentActions.createComment.promise( 215 | new Promise((resolve) => { 216 | let comment = this.insert({ 217 | content, 218 | updatedAt: new Date(Date.now()) 219 | }); 220 | resolve(comment); 221 | this.trigger(); 222 | }) 223 | ); 224 | }, 225 | 226 | onRemoveComment(commentID) { 227 | CommentActions.removeComment.promise( 228 | new Promise((resolve) => { 229 | let comment = this.removeById(commentID); 230 | resolve(comment); 231 | this.trigger(); 232 | }) 233 | ); 234 | } 235 | 236 | }); 237 | ``` 238 | 239 | 11. make Commment.es6 and CommentSite.es6 use store & actions 240 | 241 | src/js/components/Comment.es6 242 | ```javascript 243 | import React from 'react'; 244 | import CommentActions from 'actions/CommentActions'; 245 | 246 | export default React.createClass({ 247 | 248 | propTypes: { 249 | comment: React.PropTypes.shape({ 250 | content: React.PropTypes.string.isRequired, 251 | updatedAt: React.PropTypes.object.isRequired 252 | }).isRequired 253 | }, 254 | 255 | onRemove() { 256 | CommentActions.removeComment(this.props.comment.id) 257 | .then(() => { 258 | alert('removed!'); 259 | }); 260 | return false; 261 | }, 262 | 263 | render() { 264 | return ( 265 |
266 | { this.props.comment.content } - 267 | { this.props.comment.updatedAt.toDateString() } 268 | remove 269 |
270 | ); 271 | } 272 | 273 | }); 274 | ``` 275 | 276 | src/js/components/CommentSite.es6 277 | ```javascript 278 | import React from 'react'; 279 | import Reflux from 'reflux'; 280 | import _ from 'underscore'; 281 | import Comment from 'components/Comment'; 282 | import CommentStore from 'stores/CommentStore'; 283 | import CommentActions from 'actions/CommentActions'; 284 | 285 | function getStoreState() { 286 | return { 287 | comments: CommentStore.value() 288 | }; 289 | } 290 | 291 | export default React.createClass({ 292 | 293 | mixins: [ 294 | Reflux.listenTo(CommentStore, 'onStoreChange') 295 | ], 296 | 297 | getInitialState() { 298 | return getStoreState(); 299 | }, 300 | 301 | onStoreChange() { 302 | this.setState(getStoreState()); 303 | }, 304 | 305 | onCreateComment() { 306 | let content = React.findDOMNode(this.refs.newComment).value; 307 | CommentActions.createComment(content) 308 | .then(() => { 309 | alert('created!'); 310 | }); 311 | return false; 312 | }, 313 | 314 | render() { 315 | return ( 316 |
317 |

Comments

318 | { _.map(this.state.comments, comment => ( 319 | 320 | )) } 321 |
322 | 323 | 324 |
325 |
326 | ); 327 | } 328 | 329 | }); 330 | ``` 331 | 332 | 12. open demo.index.html in browser and check components are correctly operated 333 | 334 | ### Related links 335 | 336 | + [modulesDirectories option](http://webpack.github.io/docs/configuration.html#resolve-modulesdirectories) 337 | + [promise](http://www.html5rocks.com/ko/tutorials/es6/promises/) 338 | + [q](http://documentup.com/kriskowal/q/) 339 | + [flux](https://github.com/facebook/flux) 340 | + [reflux](https://github.com/spoike/refluxjs) 341 | + [react](https://github.com/facebook/react) 342 | + [underscore-db](https://github.com/typicode/underscore-db) 343 | + [reflux-todo](https://github.com/spoike/refluxjs-todo) 344 | + [Cannot resolve module 'fs'](https://github.com/webpack/jade-loader/issues/8) 345 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 ChulHee Lee 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Intro 2 | This tutorial is created to introduce some frontend libraries & frameworks. 3 | 4 | To avoid confusion, This tutorial is written simply and exclude optimization. 5 | 6 | if you want more information, look 'Related Links' placed at bottom of each tutorial. 7 | 8 | ## Table of Contents 9 | 1. [STEP1: Initialize node package](#step1-initialize-node-package) 10 | 2. [STEP2: Use another node package](#step2-use-another-node-package) 11 | 3. [STEP3: Make my package browser-executable by Webpack](#step3-make-my-package-browser-executable-by-webpack) 12 | 4. [STEP4: Use ES6 syntax by Babel](#step4-use-es6-syntax-by-babel) 13 | 5. [STEP5: Use React](#step5-use-react) 14 | 6. [STEP6: Use Style Guide by ESLint](#step6-use-style-guide-by-eslint) 15 | 7. [STEP7: Manage task by Gulp](#step7-manage-task-by-gulp) 16 | 8. [STEP8: Add Sourcemaps by Webpack](#step8-add-sourcemaps-by-webpack) 17 | 9. [STEP9: Create Simple app with Reflux & React](#step9-create-simple-app-with-reflux--react) 18 | 10. [STEP10: Make your app sync with REST API server with json-server & jquery](#step10-make-your-app-sync-with-rest-api-server-with-json-server--jquery) 19 | 20 | ## Step1: Initialize node package 21 | 22 | 1. Initailize node package. 23 | 24 | ```bash 25 | npm init # This command make package.json 26 | ``` 27 | 28 | package.json 29 | ```json 30 | { 31 | "name": "step-by-step-frontend", 32 | "version": "0.0.0", 33 | "description": "step by step learning about frontend", 34 | "main": "index.js", 35 | "author": "ironhee ", 36 | "license": "MIT" 37 | } 38 | ``` 39 | 40 | 3. Write your node module. 41 | 42 | index.js 43 | ```javascript 44 | 'use strict'; 45 | 46 | module.exports = function helloWorld() { 47 | console.log('hello world!'); 48 | }; 49 | ``` 50 | 51 | 4. load & run your module. 52 | 53 | test.js 54 | ```javascript 55 | 'use strict'; 56 | 57 | var helloWorld = require('./'); 58 | helloWorld(); 59 | ``` 60 | 61 | ```bash 62 | node test.js # hello world! 63 | ``` 64 | 65 | ### Related links 66 | 67 | + [strict mode ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode) 68 | + [npm init](https://docs.npmjs.com/cli/init) 69 | + [package.json](https://docs.npmjs.com/files/package.json) 70 | + [modules](https://nodejs.org/api/modules.html) 71 | 72 | ## Step2: Use another node package 73 | 74 | 1. Install another node package. 75 | 76 | ```bash 77 | npm install --save underscore 78 | ``` 79 | 80 | package.json 81 | ```json 82 | { 83 | "name": "step-by-step-frontend", 84 | "version": "0.0.0", 85 | "description": "step by step learning about frontend", 86 | "main": "index.js", 87 | "author": "ironhee ", 88 | "license": "MIT", 89 | "dependencies": { 90 | "underscore": "^1.8.3" 91 | } 92 | } 93 | 94 | ``` 95 | 96 | 3. update your node module. 97 | 98 | index.js 99 | ```javascript 100 | 'use strict'; 101 | 102 | var _ = require('underscore'); 103 | 104 | module.exports = function helloWorld() { 105 | _.times(10, function (index) { 106 | console.log('[' + index + '] hello world!'); 107 | }); 108 | }; 109 | ``` 110 | 111 | 4. load & run your module. 112 | 113 | test.js 114 | ```javascript 115 | 'use strict'; 116 | 117 | var helloWorld = require('./'); 118 | helloWorld(); 119 | ``` 120 | 121 | ```bash 122 | node test.js # [0] hello world! ... 123 | ``` 124 | 125 | 5. create .gitignore 126 | 127 | .gitignore 128 | ``` 129 | node_modules 130 | ``` 131 | 132 | ### Related links 133 | 134 | + [require](https://nodejs.org/api/modules.html) 135 | + [npm install](https://docs.npmjs.com/cli/install) 136 | + [dependencies](https://docs.npmjs.com/files/package.json#dependencies) 137 | 138 | ## Step3: Make my package browser-executable by Webpack 139 | 140 | 1. Install & Initailize bower package. 141 | 142 | ```bash 143 | npm install -g bower 144 | bower init # This command make bower.json 145 | ``` 146 | 147 | bower.json 148 | ```json 149 | { 150 | "name": "step-by-step-frontend", 151 | "version": "0.0.0", 152 | "description": "step by step learning about frontend", 153 | "main": "dist/index.js", 154 | "authors": [ 155 | "ironhee " 156 | ], 157 | "license": "MIT" 158 | } 159 | ``` 160 | 161 | 2. Install webpack 162 | 163 | ```bash 164 | npm install -g webpack 165 | ``` 166 | 167 | 3. create webpack config 168 | 169 | webpack.config.js 170 | ```javascript 171 | 'use strict'; 172 | 173 | var _ = require('underscore'); 174 | var pkg = require('./package.json'); 175 | 176 | module.exports = { 177 | entry: { 178 | 'index': './index.js' 179 | }, 180 | output: { 181 | path: 'dist/', 182 | filename: '[name].js', 183 | library: 'MyLib', 184 | libraryTarget: 'umd' 185 | } 186 | }; 187 | ``` 188 | 189 | 4. build source code by webpack 190 | 191 | ```bash 192 | webpack # use webpack.config.js by default 193 | ``` 194 | 195 | if you want to rebuild on file change, use --watch option 196 | ```bash 197 | webpack --watch # ctrl-c to exit 198 | ``` 199 | 200 | 5. load & run your module. 201 | 202 | test.html 203 | ```html 204 | 205 | 206 | 207 | 208 | 209 | 210 | ``` 211 | 212 | 6. change package.json 'main' property 213 | 214 | package.json 215 | ```json 216 | { 217 | "name": "step-by-step-frontend", 218 | "version": "0.0.0", 219 | "description": "step by step learning about frontend", 220 | "main": "dist/index.js", 221 | "author": "ironhee ", 222 | "license": "MIT", 223 | "dependencies": { 224 | "underscore": "^1.8.3" 225 | } 226 | } 227 | ``` 228 | 229 | 7. load & run your node module. 230 | 231 | test.js 232 | ```javascript 233 | 'use strict'; 234 | 235 | var helloWorld = require('./'); 236 | helloWorld(); 237 | ``` 238 | 239 | ```bash 240 | node test.js # [0] hello world! ... 241 | ``` 242 | 243 | ### Related links 244 | 245 | + [bower](https://github.com/bower/bower) 246 | + [webpack](https://github.com/webpack/webpack) 247 | + [webpack config](http://webpack.github.io/docs/configuration.html) 248 | 249 | ## Step4: Use ES6 syntax by Babel 250 | 251 | 1. Install babel-loader by npm 252 | 253 | ```bash 254 | npm install --save-dev babel-loader 255 | ``` 256 | 257 | 2. rename index.js and change content 258 | 259 | ```bash 260 | mv index.js index.es6 261 | ``` 262 | 263 | index.es6 264 | ```javascript 265 | import _ from 'underscore'; 266 | 267 | export default function helloWorld() { 268 | _.times(10, (index) => { 269 | console.log(`[${index}] hello world!`); 270 | }); 271 | } 272 | ``` 273 | 274 | 3. change webpack config 275 | 276 | webpack.config.js 277 | ```javascript 278 | 'use strict'; 279 | 280 | var _ = require('underscore'); 281 | var pkg = require('./package.json'); 282 | 283 | module.exports = { 284 | entry: { 285 | 'index': './index.es6' 286 | }, 287 | output: { 288 | path: 'dist/', 289 | filename: '[name].js', 290 | library: 'MyLib', 291 | libraryTarget: 'umd' 292 | }, 293 | module: { 294 | loaders: [ 295 | { test: /\.es6$/, loader: 'babel-loader' } 296 | ] 297 | } 298 | }; 299 | ``` 300 | 301 | 4. build source code by webpack 302 | 303 | ```bash 304 | webpack 305 | ``` 306 | 307 | 5. test browser-side and node-side 308 | 309 | test.html 310 | ```html 311 | 312 | 313 | 314 | 315 | 316 | 317 | ``` 318 | 319 | ```bash 320 | node test.js # [0] hello world! ... 321 | ``` 322 | 323 | ### Related links 324 | 325 | + [es6features](https://github.com/lukehoban/es6features) 326 | + [babel](https://github.com/babel/babel) 327 | + [webpack loader](http://webpack.github.io/docs/using-loaders.html) 328 | 329 | ## Step5: Use React 330 | 331 | 1. Install React by npm 332 | 333 | ```bash 334 | npm install --save react 335 | ``` 336 | 337 | 2. make some directories 338 | 339 | ```bash 340 | mkdir -p src/js/components 341 | ``` 342 | 343 | 3. create modules and rendering script 344 | 345 | src/js/components/MyComponent.es6 346 | ```javascript 347 | import React from 'react'; 348 | 349 | export default React.createClass({ 350 | 351 | render() { 352 | return ( 353 |
354 |

Hello world!

355 |
356 | ); 357 | } 358 | 359 | }); 360 | ``` 361 | 362 | src/js/app.es6 363 | ```javascript 364 | import MyComponent from './components/MyComponent'; 365 | 366 | export default { 367 | MyComponent 368 | }; 369 | ``` 370 | 371 | src/js/main.es6 372 | ```javascript 373 | import React from 'react'; 374 | import { MyComponent } from './app'; 375 | 376 | React.render(, document.body); 377 | ``` 378 | 379 | 4. remove old files and change webpack config 380 | 381 | ```bash 382 | rm -rf test.js test.html index.es6 dist/index.js 383 | ``` 384 | 385 | 5. separate webpack config 386 | 387 | webpack.base.config.js 388 | ```javascript 389 | 'use strict'; 390 | 391 | module.exports = { 392 | resolve: { 393 | extensions: ['', '.js', '.es6'] 394 | }, 395 | module: { 396 | loaders: [ 397 | { test: /\.es6$/, loader: 'babel-loader' } 398 | ] 399 | } 400 | }; 401 | ``` 402 | 403 | webpack.config.js 404 | ```javascript 405 | 'use strict'; 406 | 407 | var _ = require('underscore'); 408 | var baseConfig = require('./webpack.base.config'); 409 | 410 | module.exports = _.extend({}, baseConfig, { 411 | entry: { 412 | 'app': './src/js/app.es6' 413 | }, 414 | output: { 415 | path: 'dist/', 416 | filename: 'index.js', 417 | library: 'MyLib', 418 | libraryTarget: 'umd' 419 | } 420 | }); 421 | ``` 422 | 423 | webpack.main.config.js 424 | ```javascript 425 | 'use strict'; 426 | 427 | var _ = require('underscore'); 428 | var baseConfig = require('./webpack.base.config'); 429 | 430 | module.exports = _.extend({}, baseConfig, { 431 | entry: { 432 | 'main': './src/js/main.es6' 433 | }, 434 | output: { 435 | path: 'dist/', 436 | filename: 'main.js', 437 | libraryTarget: 'umd' 438 | } 439 | }); 440 | ``` 441 | 442 | 6. build by webpack 443 | 444 | ```bash 445 | webpack # build library code 446 | webpack --config webpack.main.config # build rendering code 447 | ``` 448 | 449 | 7. check in node and browser 450 | 451 | ```bash 452 | node -e 'console.log(require("./"))' 453 | # { MyComponent: { [Function] displayName: 'MyComponent' } } 454 | ``` 455 | 456 | demo/index.html 457 | ```html 458 | 459 | 460 | 461 | 462 | 463 | 464 | ``` 465 | 466 | 8. use main.js (rendering logic) in browser 467 | 468 | demo/index.html 469 | ```html 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | ``` 478 | 479 | 480 | ### Related links 481 | 482 | + [react](https://github.com/facebook/react) 483 | 484 | ## Step6: Use Style Guide by ESLint 485 | 486 | 1. Install eslint and plugins by npm 487 | 488 | ```bash 489 | npm install --save-dev eslint babel-eslint eslint-plugin-react 490 | ``` 491 | 492 | 2. make eslint configs 493 | 494 | .eslintrc 495 | ```json 496 | { 497 | "env": { 498 | "browser": true, 499 | "node": true 500 | }, 501 | "rules": { 502 | "strict": [2, "global"], 503 | "no-shadow": 2, 504 | "no-shadow-restricted-names": 2, 505 | "no-unused-vars": [2, { 506 | "vars": "local", 507 | "args": "after-used" 508 | }], 509 | "no-use-before-define": 2, 510 | "comma-dangle": [2, "never"], 511 | "no-cond-assign": [2, "always"], 512 | "no-console": 1, 513 | "no-debugger": 1, 514 | "no-alert": 1, 515 | "no-constant-condition": 1, 516 | "no-dupe-keys": 2, 517 | "no-duplicate-case": 2, 518 | "no-empty": 2, 519 | "no-ex-assign": 2, 520 | "no-extra-boolean-cast": 0, 521 | "no-extra-semi": 2, 522 | "no-func-assign": 2, 523 | "no-inner-declarations": 2, 524 | "no-invalid-regexp": 2, 525 | "no-irregular-whitespace": 2, 526 | "no-obj-calls": 2, 527 | "no-reserved-keys": 2, 528 | "no-sparse-arrays": 2, 529 | "no-unreachable": 2, 530 | "use-isnan": 2, 531 | "block-scoped-var": 2, 532 | "consistent-return": 2, 533 | "curly": [2, "multi-line"], 534 | "default-case": 2, 535 | "dot-notation": [2, { 536 | "allowKeywords": true 537 | }], 538 | "eqeqeq": 2, 539 | "guard-for-in": 2, 540 | "no-caller": 2, 541 | "no-else-return": 2, 542 | "no-eq-null": 2, 543 | "no-eval": 2, 544 | "no-extend-native": 2, 545 | "no-extra-bind": 2, 546 | "no-fallthrough": 2, 547 | "no-floating-decimal": 2, 548 | "no-implied-eval": 2, 549 | "no-lone-blocks": 2, 550 | "no-loop-func": 2, 551 | "no-multi-str": 2, 552 | "no-native-reassign": 2, 553 | "no-new": 2, 554 | "no-new-func": 2, 555 | "no-new-wrappers": 2, 556 | "no-octal": 2, 557 | "no-octal-escape": 2, 558 | "no-param-reassign": 2, 559 | "no-proto": 2, 560 | "no-redeclare": 2, 561 | "no-return-assign": 2, 562 | "no-script-url": 2, 563 | "no-self-compare": 2, 564 | "no-sequences": 2, 565 | "no-throw-literal": 2, 566 | "no-with": 2, 567 | "radix": 2, 568 | "vars-on-top": 2, 569 | "wrap-iife": [2, "any"], 570 | "yoda": 2, 571 | "indent": [2, 2], 572 | "brace-style": [2, 573 | "1tbs", { 574 | "allowSingleLine": true 575 | }], 576 | "quotes": [ 577 | 2, "single", "avoid-escape" 578 | ], 579 | "camelcase": [2, { 580 | "properties": "never" 581 | }], 582 | "comma-spacing": [2, { 583 | "before": false, 584 | "after": true 585 | }], 586 | "comma-style": [2, "last"], 587 | "eol-last": 2, 588 | "func-names": 1, 589 | "key-spacing": [2, { 590 | "beforeColon": false, 591 | "afterColon": true 592 | }], 593 | "new-cap": [2, { 594 | "newIsCap": true 595 | }], 596 | "no-multiple-empty-lines": [2, { 597 | "max": 2 598 | }], 599 | "no-nested-ternary": 2, 600 | "no-new-object": 2, 601 | "no-spaced-func": 2, 602 | "no-trailing-spaces": 2, 603 | "no-wrap-func": 2, 604 | "no-underscore-dangle": 0, 605 | "one-var": [2, "never"], 606 | "padded-blocks": [2, "never"], 607 | "semi": [2, "always"], 608 | "semi-spacing": [2, { 609 | "before": false, 610 | "after": true 611 | }], 612 | "space-after-keywords": 2, 613 | "space-before-blocks": 2, 614 | "space-before-function-paren": [2, "never"], 615 | "space-infix-ops": 2, 616 | "space-return-throw-case": 2, 617 | "spaced-line-comment": 2, 618 | } 619 | } 620 | ``` 621 | 622 | src/js/.estlinrc 623 | ```json 624 | { 625 | "parser": "babel-eslint", 626 | "plugins": [ 627 | "react" 628 | ], 629 | "ecmaFeatures": { 630 | "arrowFunctions": true, 631 | "blockBindings": true, 632 | "classes": true, 633 | "defaultParams": true, 634 | "destructuring": true, 635 | "forOf": true, 636 | "generators": false, 637 | "modules": true, 638 | "objectLiteralComputedProperties": true, 639 | "objectLiteralDuplicateProperties": false, 640 | "objectLiteralShorthandMethods": true, 641 | "objectLiteralShorthandProperties": true, 642 | "spread": true, 643 | "superInFunctions": true, 644 | "templateStrings": true, 645 | "jsx": true 646 | }, 647 | "rules": { 648 | "strict": [2, "never"], 649 | "no-var": 2, 650 | "react/display-name": 0, 651 | "react/jsx-boolean-value": 2, 652 | "react/jsx-quotes": [2, "double"], 653 | "react/jsx-no-undef": 2, 654 | "react/jsx-sort-props": 0, 655 | "react/jsx-sort-prop-types": 0, 656 | "react/jsx-uses-react": 2, 657 | "react/jsx-uses-vars": 2, 658 | "react/no-did-mount-set-state": [2, "allow-in-func"], 659 | "react/no-did-update-set-state": 2, 660 | "react/no-multi-comp": 2, 661 | "react/no-unknown-property": 2, 662 | "react/prop-types": 2, 663 | "react/react-in-jsx-scope": 2, 664 | "react/self-closing-comp": 2, 665 | "react/wrap-multilines": 2, 666 | "react/sort-comp": [2, { 667 | "order": [ 668 | "displayName", 669 | "mixins", 670 | "statics", 671 | "propTypes", 672 | "getDefaultProps", 673 | "getInitialState", 674 | "componentWillMount", 675 | "componentDidMount", 676 | "componentWillReceiveProps", 677 | "shouldComponentUpdate", 678 | "componentWillUpdate", 679 | "componentWillUnmount", 680 | "/^on.+$/", 681 | "/^get.+$/", 682 | "/^render.+$/", 683 | "render" 684 | ] 685 | }] 686 | } 687 | } 688 | ``` 689 | 690 | 3. install editor plugin 691 | - sublime text: Install SublimeLinter & SublimeLinter-eslint 692 | - atom: Install linter & linter-eslint 693 | 694 | ### Related links 695 | 696 | + [airbnb javascript style guide](https://github.com/airbnb/javascript) 697 | + [eslint](http://eslint.org/) 698 | + [eslint config](http://eslint.org/docs/user-guide/configuring.html) 699 | + [atom](https://atom.io/) 700 | + [sublimt text](http://www.sublimetext.com/) 701 | 702 | ## Step7: Manage task by Gulp 703 | 704 | 1. Install gulp by npm 705 | 706 | ```bash 707 | npm install -g gulp 708 | npm install --save-dev gulp 709 | ``` 710 | 711 | 2. Install webpack and plugin by npm 712 | 713 | ```bash 714 | npm install --save-dev webpack 715 | npm install --save-dev webpack-gulp-logger 716 | ``` 717 | 718 | 3. create gulp config file (gulpfile.js) 719 | 720 | gulpfile.js 721 | ```javascript 722 | 'use strict'; 723 | 724 | var gulp = require('gulp'); 725 | var webpack = require('webpack'); 726 | var webpackLogger = require('webpack-gulp-logger'); 727 | var libWebpackConfig = require('./webpack.config'); 728 | var mainWebpackConfig = require('./webpack.main.config'); 729 | 730 | gulp.task('default', [ 731 | 'watch' 732 | ]); 733 | 734 | gulp.task('watch', [ 735 | 'watch-lib', 736 | 'watch-main' 737 | ]); 738 | 739 | gulp.task('build', [ 740 | 'build-lib', 741 | 'build-main' 742 | ]); 743 | 744 | gulp.task('watch-lib', function() { 745 | webpack(libWebpackConfig).watch({}, webpackLogger()); 746 | }); 747 | 748 | gulp.task('watch-main', function() { 749 | webpack(mainWebpackConfig).watch({}, webpackLogger()); 750 | }); 751 | 752 | gulp.task('build-lib', function(callback) { 753 | webpack(libWebpackConfig).run(webpackLogger(callback)); 754 | }); 755 | 756 | gulp.task('build-main', function(callback) { 757 | webpack(mainWebpackConfig).run(webpackLogger(callback)); 758 | }); 759 | ``` 760 | 761 | 4. run gulp task 762 | 763 | for build 764 | ```bash 765 | gulp build 766 | ``` 767 | 768 | for watch 769 | ```bash 770 | gulp watch 771 | ``` 772 | 773 | ### Related links 774 | 775 | + [gulp](https://github.com/gulpjs/gulp) 776 | + [webpack api](http://webpack.github.io/docs/node.js-api.html) 777 | 778 | ## Step8: Add Sourcemaps by Webpack 779 | 780 | 1. set devtool property in webpack config 781 | 782 | webpack.base.config.js 783 | ```javascript 784 | 'use strict'; 785 | 786 | module.exports = { 787 | devtool: 'eval-source-map', 788 | resolve: { 789 | extensions: ['', '.js', '.es6'] 790 | }, 791 | module: { 792 | loaders: [ 793 | { test: /\.es6$/, loader: 'babel-loader' } 794 | ] 795 | } 796 | }; 797 | ``` 798 | 799 | 2. build by gulp 800 | 801 | ```bash 802 | gulp build 803 | ``` 804 | 805 | now you can distinguish source code. 806 | 807 | 808 | ### Related links 809 | 810 | + [sourcemap](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/?redirect_from_locale=ko) 811 | + [devtool option](http://webpack.github.io/docs/configuration.html#devtool) 812 | 813 | ## Step9: Create Simple app with Reflux & React 814 | 815 | 1. add resolve.modulesDirectories option to webpack config for convenience 816 | 817 | webpack.base.config 818 | ```javascript 819 | 'use strict'; 820 | 821 | module.exports = { 822 | devtool: 'eval-source-map', 823 | resolve: { 824 | modulesDirectories: ['src/js/', 'node_modules'], 825 | extensions: ['', '.js', '.es6'] 826 | }, 827 | module: { 828 | loaders: [ 829 | { test: /\.es6$/, loader: 'babel-loader' } 830 | ] 831 | } 832 | }; 833 | ``` 834 | 835 | 2. create Commment.es6 and CommentSite.es6 836 | 837 | src/js/components/Comment.es6 838 | ```javascript 839 | import React from 'react'; 840 | 841 | export default React.createClass({ 842 | 843 | propTypes: { 844 | comment: React.PropTypes.shape({ 845 | content: React.PropTypes.string.isRequired, 846 | updatedAt: React.PropTypes.object.isRequired 847 | }).isRequired 848 | }, 849 | 850 | render() { 851 | return ( 852 |
853 | { this.props.comment.content } - 854 | { this.props.comment.updatedAt.toDateString() } 855 | remove 856 |
857 | ); 858 | } 859 | 860 | }); 861 | ``` 862 | 863 | src/js/components/CommentSite.es6 864 | ```javascript 865 | import React from 'react'; 866 | import _ from 'underscore'; 867 | import Comment from 'components/Comment'; 868 | 869 | export default React.createClass({ 870 | 871 | getInitialState() { 872 | return { 873 | comments: [{ 874 | id: 1, 875 | content: 'this is comment1!', 876 | updatedAt: new Date(Date.now()) 877 | }, { 878 | id: 2, 879 | content: 'this is comment2!', 880 | updatedAt: new Date(Date.now()) 881 | }] 882 | }; 883 | }, 884 | 885 | render() { 886 | return ( 887 |
888 |

Comments

889 | { _.map(this.state.comments, comment => ( 890 | 891 | )) } 892 |
893 | 894 | 895 |
896 |
897 | ); 898 | } 899 | 900 | }); 901 | ``` 902 | 903 | 3. add CommentSite to app.es6 904 | 905 | src/js/app.es6 906 | ```javascript 907 | import MyComponent from 'components/MyComponent'; 908 | import CommentSite from 'components/CommentSite'; 909 | 910 | export default { 911 | MyComponent, 912 | CommentSite 913 | }; 914 | ``` 915 | 916 | 4. change main.es6 917 | 918 | main.es6 919 | ```javascript 920 | import React from 'react'; 921 | import { CommentSite } from 'app'; 922 | 923 | React.render(, document.body); 924 | ``` 925 | 926 | 5. open demo.index.html in browser and check components are correctly rendered 927 | 928 | 6. install reflux, q, underscore-db by npm 929 | 930 | ```bash 931 | npm install --save reflux q@~1.0 underscore-db 932 | ``` 933 | 934 | 7. add node.fs option to webpack config 935 | 936 | webpack.base.config 937 | ```javascript 938 | 'use strict'; 939 | 940 | module.exports = { 941 | devtool: 'eval-source-map', 942 | node: { 943 | fs: 'empty' 944 | }, 945 | resolve: { 946 | modulesDirectories: ['src/js/', 'node_modules'], 947 | extensions: ['', '.js', '.es6'] 948 | }, 949 | module: { 950 | loaders: [ 951 | { test: /\.es6$/, loader: 'babel-loader' } 952 | ] 953 | } 954 | }; 955 | ``` 956 | 957 | 8. define comment actions 958 | 959 | src/js/actions/CommentActions.es6 960 | ```javascript 961 | import Reflux from 'reflux'; 962 | 963 | export default Reflux.createActions({ 964 | 965 | createComment: { 966 | asyncResult: true 967 | }, 968 | 969 | removeComment: { 970 | asyncResult: true 971 | } 972 | 973 | }); 974 | ``` 975 | 976 | 9. set reflux promise factory to Q.Promise 977 | 978 | src/js/app.es6 979 | ```javascript 980 | import Reflux from 'reflux'; 981 | import Q from 'q'; 982 | Reflux.setPromiseFactory(Q.Promise); 983 | 984 | import MyComponent from 'components/MyComponent'; 985 | import CommentSite from 'components/CommentSite'; 986 | 987 | export default { 988 | MyComponent, 989 | CommentSite 990 | }; 991 | ``` 992 | 993 | 10. create comment store 994 | 995 | src/js/mixins/DBMixin.es6 996 | ```javascript 997 | import underscoreDB from 'underscore-db'; 998 | import _ from 'underscore'; 999 | 1000 | _.mixin(underscoreDB); 1001 | 1002 | export default function DBMixin() { 1003 | let result = { 1004 | db: [] 1005 | }; 1006 | 1007 | _.extend(result, _(result.db)); 1008 | return result; 1009 | } 1010 | ``` 1011 | 1012 | src/js/stores/CommentStore.es6 1013 | ```javascript 1014 | import Reflux from 'reflux'; 1015 | import CommentActions from 'actions/CommentActions'; 1016 | import DBMixin from 'mixins/DBMixin'; 1017 | import { Promise } from 'q'; 1018 | 1019 | export default Reflux.createStore({ 1020 | 1021 | mixins: [new DBMixin()], 1022 | 1023 | listenables: [CommentActions], 1024 | 1025 | onCreateComment(content) { 1026 | CommentActions.createComment.promise( 1027 | new Promise((resolve) => { 1028 | let comment = this.insert({ 1029 | content, 1030 | updatedAt: new Date(Date.now()) 1031 | }); 1032 | resolve(comment); 1033 | this.trigger(); 1034 | }) 1035 | ); 1036 | }, 1037 | 1038 | onRemoveComment(commentID) { 1039 | CommentActions.removeComment.promise( 1040 | new Promise((resolve) => { 1041 | let comment = this.removeById(commentID); 1042 | resolve(comment); 1043 | this.trigger(); 1044 | }) 1045 | ); 1046 | } 1047 | 1048 | }); 1049 | ``` 1050 | 1051 | 11. make Commment.es6 and CommentSite.es6 use store & actions 1052 | 1053 | src/js/components/Comment.es6 1054 | ```javascript 1055 | import React from 'react'; 1056 | import CommentActions from 'actions/CommentActions'; 1057 | 1058 | export default React.createClass({ 1059 | 1060 | propTypes: { 1061 | comment: React.PropTypes.shape({ 1062 | content: React.PropTypes.string.isRequired, 1063 | updatedAt: React.PropTypes.object.isRequired 1064 | }).isRequired 1065 | }, 1066 | 1067 | onRemove() { 1068 | CommentActions.removeComment(this.props.comment.id) 1069 | .then(() => { 1070 | alert('removed!'); 1071 | }); 1072 | return false; 1073 | }, 1074 | 1075 | render() { 1076 | return ( 1077 |
1078 | { this.props.comment.content } - 1079 | { this.props.comment.updatedAt.toDateString() } 1080 | remove 1081 |
1082 | ); 1083 | } 1084 | 1085 | }); 1086 | ``` 1087 | 1088 | src/js/components/CommentSite.es6 1089 | ```javascript 1090 | import React from 'react'; 1091 | import Reflux from 'reflux'; 1092 | import _ from 'underscore'; 1093 | import Comment from 'components/Comment'; 1094 | import CommentStore from 'stores/CommentStore'; 1095 | import CommentActions from 'actions/CommentActions'; 1096 | 1097 | function getStoreState() { 1098 | return { 1099 | comments: CommentStore.value() 1100 | }; 1101 | } 1102 | 1103 | export default React.createClass({ 1104 | 1105 | mixins: [ 1106 | Reflux.listenTo(CommentStore, 'onStoreChange') 1107 | ], 1108 | 1109 | getInitialState() { 1110 | return getStoreState(); 1111 | }, 1112 | 1113 | onStoreChange() { 1114 | this.setState(getStoreState()); 1115 | }, 1116 | 1117 | onCreateComment() { 1118 | let content = React.findDOMNode(this.refs.newComment).value; 1119 | CommentActions.createComment(content) 1120 | .then(() => { 1121 | alert('created!'); 1122 | }); 1123 | return false; 1124 | }, 1125 | 1126 | render() { 1127 | return ( 1128 |
1129 |

Comments

1130 | { _.map(this.state.comments, comment => ( 1131 | 1132 | )) } 1133 |
1134 | 1135 | 1136 |
1137 |
1138 | ); 1139 | } 1140 | 1141 | }); 1142 | ``` 1143 | 1144 | 12. open demo.index.html in browser and check components are correctly operated 1145 | 1146 | ### Related links 1147 | 1148 | + [modulesDirectories option](http://webpack.github.io/docs/configuration.html#resolve-modulesdirectories) 1149 | + [promise](http://www.html5rocks.com/ko/tutorials/es6/promises/) 1150 | + [q](http://documentup.com/kriskowal/q/) 1151 | + [flux](https://github.com/facebook/flux) 1152 | + [reflux](https://github.com/spoike/refluxjs) 1153 | + [react](https://github.com/facebook/react) 1154 | + [underscore-db](https://github.com/typicode/underscore-db) 1155 | + [reflux-todo](https://github.com/spoike/refluxjs-todo) 1156 | + [Cannot resolve module 'fs'](https://github.com/webpack/jade-loader/issues/8) 1157 | 1158 | ## STEP10: Make your app sync with REST API server with json-server & jquery 1159 | 1160 | 1. Install json-server globally by npm 1161 | 1162 | ```bash 1163 | npm install -g json-server 1164 | ``` 1165 | 1166 | 2. Create directory for json-server 1167 | 1168 | ```bash 1169 | mkdir public 1170 | ``` 1171 | 1172 | 3. Move demo/index.html to public/index.html 1173 | 1174 | ```bash 1175 | mv demo/index.html public 1176 | rm -rf demo 1177 | ``` 1178 | 1179 | 4. Change content of public/index.html 1180 | 1181 | public/index.html 1182 | ```html 1183 | 1184 | 1185 | 1186 | 1187 | 1188 | ``` 1189 | 1190 | 5. Make symbolic link of static files. 1191 | 1192 | ``` 1193 | ln -s ../dist/ public/static 1194 | ``` 1195 | 1196 | 6. create db.json 1197 | 1198 | db.json 1199 | ```json 1200 | {} 1201 | ``` 1202 | 1203 | 7. Run json-server 1204 | 1205 | ```bash 1206 | json-server db.json 1207 | # {^_^} Hi! 1208 | # 1209 | # Loading database from db.json 1210 | # 1211 | # 1212 | # You can now go to http://localhost:3000 1213 | # 1214 | # Enter s at any time to create a snapshot # of the db 1215 | ``` 1216 | 1217 | 8. Open http://localhost:3000 in browser. and check your app is correctly operated. 1218 | 1219 | 9. Install jquery, url-join by npm 1220 | 1221 | ```bash 1222 | npm install -S jquery url-join 1223 | ``` 1224 | 1225 | 10. Make CommentStore use Ajax Request. 1226 | 1227 | src/js/mixins/DBMixin.es6 1228 | ```javascript 1229 | import underscoreDB from 'underscore-db'; 1230 | import _ from 'underscore'; 1231 | import $ from 'jquery'; 1232 | import urlJoin from 'url-join'; 1233 | import { Promise } from 'q'; 1234 | 1235 | _.mixin(underscoreDB); 1236 | 1237 | function ajaxRequest(options) { 1238 | return new Promise((resolve, reject) => { 1239 | $.ajax(options) 1240 | .then(resolve) 1241 | .fail(reject); 1242 | }); 1243 | } 1244 | 1245 | export default function DBMixin(type) { 1246 | let result = { 1247 | db: [] 1248 | }; 1249 | let methods = _(result.db); 1250 | _.extend(result, methods); 1251 | _.extend(result, { 1252 | insert(attributes) { 1253 | return ajaxRequest({ 1254 | type: 'POST', 1255 | url: urlJoin(type), 1256 | data: attributes 1257 | }) 1258 | .then(response => { 1259 | return response; 1260 | }) 1261 | .then(response => methods.insert(response)); 1262 | }, 1263 | removeById(id) { 1264 | return ajaxRequest({ 1265 | type: 'DELETE', 1266 | url: urlJoin(type, id) 1267 | }) 1268 | .then(() => methods.removeById(id)); 1269 | } 1270 | }); 1271 | 1272 | return result; 1273 | } 1274 | ``` 1275 | 1276 | src/js/stores/CommentStore.es6 1277 | ```javascript 1278 | import Reflux from 'reflux'; 1279 | import CommentActions from 'actions/CommentActions'; 1280 | import DBMixin from 'mixins/DBMixin'; 1281 | import { Promise } from 'q'; 1282 | 1283 | export default Reflux.createStore({ 1284 | 1285 | mixins: [new DBMixin('comments')], 1286 | 1287 | listenables: [CommentActions], 1288 | 1289 | onCreateComment(content) { 1290 | CommentActions.createComment.promise( 1291 | new Promise((resolve, reject) => { 1292 | this.insert({ 1293 | content, 1294 | updatedAt: new Date().getTime() 1295 | }) 1296 | .then(comment => resolve(comment)) 1297 | .then(() => this.trigger()) 1298 | .catch(reject); 1299 | }) 1300 | ); 1301 | }, 1302 | 1303 | onRemoveComment(commentID) { 1304 | CommentActions.removeComment.promise( 1305 | new Promise((resolve, reject) => { 1306 | this.removeById(commentID) 1307 | .then(comment => resolve(comment)) 1308 | .then(() => this.trigger()) 1309 | .catch(reject); 1310 | }) 1311 | ); 1312 | } 1313 | 1314 | }); 1315 | ``` 1316 | 1317 | Warning: comment.updatedAt field's type is change. 1318 | 1319 | 11. Apply comment.updatedAt field's type change. 1320 | 1321 | src/js/components/Comment.es6 1322 | ```javascript 1323 | import React from 'react'; 1324 | import CommentActions from 'actions/CommentActions'; 1325 | 1326 | export default React.createClass({ 1327 | 1328 | propTypes: { 1329 | comment: React.PropTypes.shape({ 1330 | content: React.PropTypes.string.isRequired, 1331 | updatedAt: React.PropTypes.number.isRequired 1332 | }).isRequired 1333 | }, 1334 | 1335 | onRemove() { 1336 | CommentActions.removeComment(this.props.comment.id) 1337 | .then(() => { 1338 | alert('removed!'); 1339 | }); 1340 | return false; 1341 | }, 1342 | 1343 | render() { 1344 | return ( 1345 |
1346 | { this.props.comment.content } - 1347 | { new Date(this.props.comment.updatedAt).toDateString() } 1348 | remove 1349 |
1350 | ); 1351 | } 1352 | 1353 | }); 1354 | ``` 1355 | 1356 | 12. Open http://localhost:3000 in browser. and check your app make ajax request correctly. 1357 | 1358 | 14. add fetchComments action to CommentActions 1359 | 1360 | src/js/actions/CommentActions.es6 1361 | ```javascript 1362 | import Reflux from 'reflux'; 1363 | 1364 | export default Reflux.createActions({ 1365 | 1366 | fetchComments: { 1367 | asyncResult: true 1368 | }, 1369 | 1370 | createComment: { 1371 | asyncResult: true 1372 | }, 1373 | 1374 | removeComment: { 1375 | asyncResult: true 1376 | } 1377 | 1378 | }); 1379 | ``` 1380 | 1381 | 15. make CommentSite trigger fetchComment action after rendered. (componentDidMount) 1382 | 1383 | src/js/components/CommentSite.es6 1384 | ```javascript 1385 | import React from 'react'; 1386 | import Reflux from 'reflux'; 1387 | import _ from 'underscore'; 1388 | import Comment from 'components/Comment'; 1389 | import CommentStore from 'stores/CommentStore'; 1390 | import CommentActions from 'actions/CommentActions'; 1391 | 1392 | function getStoreState() { 1393 | return { 1394 | comments: CommentStore.value() 1395 | }; 1396 | } 1397 | 1398 | export default React.createClass({ 1399 | 1400 | mixins: [ 1401 | Reflux.listenTo(CommentStore, 'onStoreChange') 1402 | ], 1403 | 1404 | getInitialState() { 1405 | return getStoreState(); 1406 | }, 1407 | 1408 | componentDidMount() { 1409 | CommentActions.fetchComments(); 1410 | }, 1411 | 1412 | onStoreChange() { 1413 | this.setState(getStoreState()); 1414 | }, 1415 | 1416 | onCreateComment() { 1417 | let content = React.findDOMNode(this.refs.newComment).value; 1418 | CommentActions.createComment(content) 1419 | .then(() => { 1420 | alert('created!'); 1421 | }); 1422 | return false; 1423 | }, 1424 | 1425 | render() { 1426 | return ( 1427 |
1428 |

Comments

1429 | { _.map(this.state.comments, comment => ( 1430 | 1431 | )) } 1432 |
1433 | 1434 | 1435 |
1436 |
1437 | ); 1438 | } 1439 | 1440 | }); 1441 | ``` 1442 | 1443 | 16. implement fetch method 1444 | 1445 | src/js/mixins/DBMixin.es6 1446 | ```javascript 1447 | import underscoreDB from 'underscore-db'; 1448 | import _ from 'underscore'; 1449 | import $ from 'jquery'; 1450 | import urlJoin from 'url-join'; 1451 | import { Promise } from 'q'; 1452 | 1453 | _.mixin(underscoreDB); 1454 | 1455 | function ajaxRequest(options) { 1456 | return new Promise((resolve, reject) => { 1457 | $.ajax(options) 1458 | .then(resolve) 1459 | .fail(reject); 1460 | }); 1461 | } 1462 | 1463 | export default function DBMixin(type) { 1464 | let result = { 1465 | db: [] 1466 | }; 1467 | let methods = _(result.db); 1468 | _.extend(result, methods); 1469 | _.extend(result, { 1470 | insert(attributes) { 1471 | return ajaxRequest({ 1472 | type: 'POST', 1473 | url: urlJoin(type), 1474 | data: attributes 1475 | }) 1476 | .then(response => { 1477 | return response; 1478 | }) 1479 | .then(response => methods.insert(response)); 1480 | }, 1481 | removeById(id) { 1482 | return ajaxRequest({ 1483 | type: 'DELETE', 1484 | url: urlJoin(type, id) 1485 | }) 1486 | .then(() => methods.removeById(id)); 1487 | }, 1488 | fetch(id) { 1489 | return ajaxRequest({ 1490 | type: 'GET', 1491 | url: urlJoin(type, id) 1492 | }) 1493 | .then(response => _.isArray(response) ? 1494 | _.map(response, _response => methods.insert(_response)) : 1495 | methods.insert(response) 1496 | ); 1497 | } 1498 | }); 1499 | 1500 | return result; 1501 | } 1502 | ``` 1503 | 1504 | src/js/stores/CommentStore.es6 1505 | ```javascript 1506 | import Reflux from 'reflux'; 1507 | import CommentActions from 'actions/CommentActions'; 1508 | import DBMixin from 'mixins/DBMixin'; 1509 | import { Promise } from 'q'; 1510 | 1511 | export default Reflux.createStore({ 1512 | 1513 | mixins: [new DBMixin('comments')], 1514 | 1515 | listenables: [CommentActions], 1516 | 1517 | onFetchComments() { 1518 | CommentActions.fetchComments.promise( 1519 | new Promise((resolve, reject) => { 1520 | this.fetch() 1521 | .then(comments => resolve(comments)) 1522 | .then(() => this.trigger()) 1523 | .catch(reject); 1524 | }) 1525 | ); 1526 | }, 1527 | 1528 | onCreateComment(content) { 1529 | CommentActions.createComment.promise( 1530 | new Promise((resolve, reject) => { 1531 | this.insert({ 1532 | content, 1533 | updatedAt: new Date().getTime() 1534 | }) 1535 | .then(comment => resolve(comment)) 1536 | .then(() => this.trigger()) 1537 | .catch(reject); 1538 | }) 1539 | ); 1540 | }, 1541 | 1542 | onRemoveComment(commentID) { 1543 | CommentActions.removeComment.promise( 1544 | new Promise((resolve, reject) => { 1545 | this.removeById(commentID) 1546 | .then(comment => resolve(comment)) 1547 | .then(() => this.trigger()) 1548 | .catch(reject); 1549 | }) 1550 | ); 1551 | } 1552 | 1553 | }); 1554 | ``` 1555 | 1556 | 17. Open http://localhost:3000 in browser. and check your app make get request after initial rendering and your comments is correctly rendered. 1557 | 1558 | 18. add db.json to .gitignore 1559 | 1560 | .gitignore 1561 | ``` 1562 | node_modules 1563 | db.json 1564 | ``` 1565 | 1566 | ### Related links 1567 | 1568 | + [url-join](https://github.com/jfromaniello/url-join) 1569 | + [json-server](https://github.com/typicode/json-server) 1570 | + [jquery](https://github.com/jquery/jquery) 1571 | + [ajax](https://developer.mozilla.org/en-US/docs/AJAX) 1572 | + [what-exactly-is-restful-programming](http://stackoverflow.com/questions/671118/what-exactly-is-restful-programming) 1573 | + [RFC2616 - Method](http://tools.ietf.org/html/rfc2616#section-9) 1574 | + [jsonapi](http://jsonapi.org/) 1575 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "step-by-step-frontend", 3 | "version": "0.0.0", 4 | "description": "step by step learning about frontend", 5 | "main": "dist/index.js", 6 | "authors": [ 7 | "ironhee " 8 | ], 9 | "license": "MIT" 10 | } 11 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | var concat = require('gulp-concat'); 5 | var webpack = require('webpack'); 6 | var webpackLogger = require('webpack-gulp-logger'); 7 | var libWebpackConfig = require('./webpack.config'); 8 | var mainWebpackConfig = require('./webpack.main.config'); 9 | 10 | gulp.task('default', [ 11 | 'watch' 12 | ]); 13 | 14 | gulp.task('watch', [ 15 | 'watch-lib', 16 | 'watch-main' 17 | ]); 18 | 19 | gulp.task('build', [ 20 | 'build-lib', 21 | 'build-main', 22 | 'build-readme' 23 | ]); 24 | 25 | gulp.task('watch-lib', function() { 26 | webpack(libWebpackConfig).watch({}, webpackLogger()); 27 | }); 28 | 29 | gulp.task('watch-main', function() { 30 | webpack(mainWebpackConfig).watch({}, webpackLogger()); 31 | }); 32 | 33 | gulp.task('build-lib', function(callback) { 34 | webpack(libWebpackConfig).run(webpackLogger(callback)); 35 | }); 36 | 37 | gulp.task('build-main', function(callback) { 38 | webpack(mainWebpackConfig).run(webpackLogger(callback)); 39 | }); 40 | 41 | gulp.task('build-readme', function(callback) { 42 | return gulp.src([ 43 | 'DOC/INTRO.md', 44 | 'DOC/STEP1.md', 45 | 'DOC/STEP2.md', 46 | 'DOC/STEP3.md', 47 | 'DOC/STEP4.md', 48 | 'DOC/STEP5.md', 49 | 'DOC/STEP6.md', 50 | 'DOC/STEP7.md', 51 | 'DOC/STEP8.md', 52 | 'DOC/STEP9.md', 53 | 'DOC/STEP10.md', 54 | ]) 55 | .pipe(concat('README.md')) 56 | .pipe(gulp.dest('./')); 57 | }); 58 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "step-by-step-frontend", 3 | "version": "0.0.0", 4 | "description": "step by step learning about frontend", 5 | "main": "dist/index.js", 6 | "author": "ironhee ", 7 | "license": "MIT", 8 | "dependencies": { 9 | "jquery": "^2.1.4", 10 | "q": "^1.0.1", 11 | "react": "^0.13.3", 12 | "reflux": "^0.2.8", 13 | "underscore": "^1.8.3", 14 | "underscore-db": "^0.9.0", 15 | "url-join": "0.0.1" 16 | }, 17 | "devDependencies": { 18 | "babel-eslint": "^3.1.17", 19 | "babel-loader": "^5.1.4", 20 | "eslint": "^0.23.0", 21 | "eslint-plugin-react": "^2.5.2", 22 | "gulp": "^3.9.0", 23 | "gulp-concat": "^2.6.0", 24 | "node-libs-browser": "^0.5.2", 25 | "webpack": "^1.9.11", 26 | "webpack-gulp-logger": "0.0.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/static: -------------------------------------------------------------------------------- 1 | ../dist/ -------------------------------------------------------------------------------- /src/js/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "plugins": [ 4 | "react" 5 | ], 6 | "ecmaFeatures": { 7 | "arrowFunctions": true, 8 | "blockBindings": true, 9 | "classes": true, 10 | "defaultParams": true, 11 | "destructuring": true, 12 | "forOf": true, 13 | "generators": false, 14 | "modules": true, 15 | "objectLiteralComputedProperties": true, 16 | "objectLiteralDuplicateProperties": false, 17 | "objectLiteralShorthandMethods": true, 18 | "objectLiteralShorthandProperties": true, 19 | "spread": true, 20 | "superInFunctions": true, 21 | "templateStrings": true, 22 | "jsx": true 23 | }, 24 | "rules": { 25 | "strict": [2, "never"], 26 | "no-var": 2, 27 | "react/display-name": 0, 28 | "react/jsx-boolean-value": 2, 29 | "react/jsx-quotes": [2, "double"], 30 | "react/jsx-no-undef": 2, 31 | "react/jsx-sort-props": 0, 32 | "react/jsx-sort-prop-types": 0, 33 | "react/jsx-uses-react": 2, 34 | "react/jsx-uses-vars": 2, 35 | "react/no-did-mount-set-state": [2, "allow-in-func"], 36 | "react/no-did-update-set-state": 2, 37 | "react/no-multi-comp": 2, 38 | "react/no-unknown-property": 2, 39 | "react/prop-types": 2, 40 | "react/react-in-jsx-scope": 2, 41 | "react/self-closing-comp": 2, 42 | "react/wrap-multilines": 2, 43 | "react/sort-comp": [2, { 44 | "order": [ 45 | "displayName", 46 | "mixins", 47 | "statics", 48 | "propTypes", 49 | "getDefaultProps", 50 | "getInitialState", 51 | "componentWillMount", 52 | "componentDidMount", 53 | "componentWillReceiveProps", 54 | "shouldComponentUpdate", 55 | "componentWillUpdate", 56 | "componentWillUnmount", 57 | "/^on.+$/", 58 | "/^get.+$/", 59 | "/^render.+$/", 60 | "render" 61 | ] 62 | }] 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/js/actions/CommentActions.es6: -------------------------------------------------------------------------------- 1 | import Reflux from 'reflux'; 2 | 3 | export default Reflux.createActions({ 4 | 5 | fetchComments: { 6 | asyncResult: true 7 | }, 8 | 9 | createComment: { 10 | asyncResult: true 11 | }, 12 | 13 | removeComment: { 14 | asyncResult: true 15 | } 16 | 17 | }); 18 | -------------------------------------------------------------------------------- /src/js/app.es6: -------------------------------------------------------------------------------- 1 | import Reflux from 'reflux'; 2 | import Q from 'q'; 3 | Reflux.setPromiseFactory(Q.Promise); 4 | 5 | import MyComponent from 'components/MyComponent'; 6 | import CommentSite from 'components/CommentSite'; 7 | 8 | export default { 9 | MyComponent, 10 | CommentSite 11 | }; 12 | -------------------------------------------------------------------------------- /src/js/components/Comment.es6: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import CommentActions from 'actions/CommentActions'; 3 | 4 | export default React.createClass({ 5 | 6 | propTypes: { 7 | comment: React.PropTypes.shape({ 8 | content: React.PropTypes.string.isRequired, 9 | updatedAt: React.PropTypes.number.isRequired 10 | }).isRequired 11 | }, 12 | 13 | onRemove() { 14 | CommentActions.removeComment(this.props.comment.id) 15 | .then(() => { 16 | alert('removed!'); 17 | }); 18 | return false; 19 | }, 20 | 21 | render() { 22 | return ( 23 |
24 | { this.props.comment.content } - 25 | { new Date(this.props.comment.updatedAt).toDateString() } 26 | remove 27 |
28 | ); 29 | } 30 | 31 | }); 32 | -------------------------------------------------------------------------------- /src/js/components/CommentSite.es6: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Reflux from 'reflux'; 3 | import _ from 'underscore'; 4 | import Comment from 'components/Comment'; 5 | import CommentStore from 'stores/CommentStore'; 6 | import CommentActions from 'actions/CommentActions'; 7 | 8 | function getStoreState() { 9 | return { 10 | comments: CommentStore.value() 11 | }; 12 | } 13 | 14 | export default React.createClass({ 15 | 16 | mixins: [ 17 | Reflux.listenTo(CommentStore, 'onStoreChange') 18 | ], 19 | 20 | getInitialState() { 21 | return getStoreState(); 22 | }, 23 | 24 | componentDidMount() { 25 | CommentActions.fetchComments(); 26 | }, 27 | 28 | onStoreChange() { 29 | this.setState(getStoreState()); 30 | }, 31 | 32 | onCreateComment() { 33 | let content = React.findDOMNode(this.refs.newComment).value; 34 | CommentActions.createComment(content) 35 | .then(() => { 36 | alert('created!'); 37 | }); 38 | return false; 39 | }, 40 | 41 | render() { 42 | return ( 43 |
44 |

Comments

45 | { _.map(this.state.comments, comment => ( 46 | 47 | )) } 48 |
49 | 50 | 51 |
52 |
53 | ); 54 | } 55 | 56 | }); 57 | -------------------------------------------------------------------------------- /src/js/components/MyComponent.es6: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default React.createClass({ 4 | 5 | render() { 6 | return ( 7 |
8 |

Hello world!

9 |
10 | ); 11 | } 12 | 13 | }); 14 | -------------------------------------------------------------------------------- /src/js/main.es6: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { CommentSite } from 'app'; 3 | 4 | React.render(, document.body); 5 | -------------------------------------------------------------------------------- /src/js/mixins/DBMixin.es6: -------------------------------------------------------------------------------- 1 | import underscoreDB from 'underscore-db'; 2 | import _ from 'underscore'; 3 | import $ from 'jquery'; 4 | import urlJoin from 'url-join'; 5 | import { Promise } from 'q'; 6 | 7 | _.mixin(underscoreDB); 8 | 9 | function ajaxRequest(options) { 10 | return new Promise((resolve, reject) => { 11 | $.ajax(options) 12 | .then(resolve) 13 | .fail(reject); 14 | }); 15 | } 16 | 17 | export default function DBMixin(type) { 18 | let result = { 19 | db: [] 20 | }; 21 | let methods = _(result.db); 22 | _.extend(result, methods); 23 | _.extend(result, { 24 | insert(attributes) { 25 | return ajaxRequest({ 26 | type: 'POST', 27 | url: urlJoin(type), 28 | data: attributes 29 | }) 30 | .then(response => { 31 | return response; 32 | }) 33 | .then(response => methods.insert(response)); 34 | }, 35 | removeById(id) { 36 | return ajaxRequest({ 37 | type: 'DELETE', 38 | url: urlJoin(type, id) 39 | }) 40 | .then(() => methods.removeById(id)); 41 | }, 42 | fetch(id) { 43 | return ajaxRequest({ 44 | type: 'GET', 45 | url: urlJoin(type, id) 46 | }) 47 | .then(response => _.isArray(response) ? 48 | _.map(response, _response => methods.insert(_response)) : 49 | methods.insert(response) 50 | ); 51 | } 52 | }); 53 | 54 | return result; 55 | } 56 | -------------------------------------------------------------------------------- /src/js/stores/CommentStore.es6: -------------------------------------------------------------------------------- 1 | import Reflux from 'reflux'; 2 | import CommentActions from 'actions/CommentActions'; 3 | import DBMixin from 'mixins/DBMixin'; 4 | import { Promise } from 'q'; 5 | 6 | export default Reflux.createStore({ 7 | 8 | mixins: [new DBMixin('comments')], 9 | 10 | listenables: [CommentActions], 11 | 12 | onFetchComments() { 13 | CommentActions.fetchComments.promise( 14 | new Promise((resolve, reject) => { 15 | this.fetch() 16 | .then(comments => resolve(comments)) 17 | .then(() => this.trigger()) 18 | .catch(reject); 19 | }) 20 | ); 21 | }, 22 | 23 | onCreateComment(content) { 24 | CommentActions.createComment.promise( 25 | new Promise((resolve, reject) => { 26 | this.insert({ 27 | content, 28 | updatedAt: new Date().getTime() 29 | }) 30 | .then(comment => resolve(comment)) 31 | .then(() => this.trigger()) 32 | .catch(reject); 33 | }) 34 | ); 35 | }, 36 | 37 | onRemoveComment(commentID) { 38 | CommentActions.removeComment.promise( 39 | new Promise((resolve, reject) => { 40 | this.removeById(commentID) 41 | .then(comment => resolve(comment)) 42 | .then(() => this.trigger()) 43 | .catch(reject); 44 | }) 45 | ); 46 | } 47 | 48 | }); 49 | -------------------------------------------------------------------------------- /webpack.base.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | devtool: 'eval-source-map', 5 | node: { 6 | fs: 'empty' 7 | }, 8 | resolve: { 9 | modulesDirectories: ['src/js/', 'node_modules'], 10 | extensions: ['', '.js', '.es6'] 11 | }, 12 | module: { 13 | loaders: [ 14 | { test: /\.es6$/, loader: 'babel-loader' } 15 | ] 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('underscore'); 4 | var baseConfig = require('./webpack.base.config'); 5 | 6 | module.exports = _.extend({}, baseConfig, { 7 | entry: { 8 | 'app': './src/js/app.es6' 9 | }, 10 | output: { 11 | path: 'dist/', 12 | filename: 'index.js', 13 | library: 'MyLib', 14 | libraryTarget: 'umd' 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /webpack.main.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('underscore'); 4 | var baseConfig = require('./webpack.base.config'); 5 | 6 | module.exports = _.extend({}, baseConfig, { 7 | entry: { 8 | 'main': './src/js/main.es6' 9 | }, 10 | output: { 11 | path: 'dist/', 12 | filename: 'main.js', 13 | libraryTarget: 'umd' 14 | } 15 | }); 16 | --------------------------------------------------------------------------------