",
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------