├── .github └── ISSUE_TEMPLATE ├── .gitignore ├── .travis.yml ├── README.md ├── how-to-translate.md ├── img ├── chai.png ├── eslint.png ├── flow.png ├── gulp.png ├── js.png ├── mocha.png ├── npm.png ├── react.png ├── redux.png ├── webpack.png └── yarn.png ├── mdlint.js ├── package.json ├── tutorial ├── 1-node-npm-yarn-package-json │ ├── README.md │ ├── index.js │ └── package.json ├── 10-immutable-redux-improvements │ ├── .gitignore │ ├── README.md │ ├── dist │ │ └── index.html │ ├── gulpfile.babel.js │ ├── package.json │ ├── src │ │ ├── client │ │ │ ├── actions │ │ │ │ └── dog-actions.js │ │ │ ├── app.jsx │ │ │ ├── components │ │ │ │ ├── button.jsx │ │ │ │ └── message.jsx │ │ │ ├── containers │ │ │ │ ├── bark-button.js │ │ │ │ └── bark-message.js │ │ │ └── reducers │ │ │ │ └── dog-reducer.js │ │ ├── server │ │ │ └── index.js │ │ └── shared │ │ │ └── dog.js │ ├── webpack.config.babel.js │ └── yarn.lock ├── 11-testing-mocha-chai-sinon │ ├── .gitignore │ ├── README.md │ ├── dist │ │ └── index.html │ ├── gulpfile.babel.js │ ├── package.json │ ├── src │ │ ├── client │ │ │ ├── actions │ │ │ │ └── dog-actions.js │ │ │ ├── app.jsx │ │ │ ├── components │ │ │ │ ├── button.jsx │ │ │ │ └── message.jsx │ │ │ ├── containers │ │ │ │ ├── bark-button.js │ │ │ │ └── bark-message.js │ │ │ └── reducers │ │ │ │ └── dog-reducer.js │ │ ├── server │ │ │ └── index.js │ │ ├── shared │ │ │ └── dog.js │ │ └── test │ │ │ ├── client │ │ │ └── state-test.js │ │ │ └── shared │ │ │ └── dog-test.js │ ├── webpack.config.babel.js │ └── yarn.lock ├── 12-flow │ ├── .flowconfig │ ├── .gitignore │ ├── README.md │ ├── dist │ │ ├── client-bundle.js.map │ │ └── index.html │ ├── gulpfile.babel.js │ ├── package.json │ ├── src │ │ ├── client │ │ │ ├── actions │ │ │ │ └── dog-actions.js │ │ │ ├── app.jsx │ │ │ ├── components │ │ │ │ ├── button.jsx │ │ │ │ └── message.jsx │ │ │ ├── containers │ │ │ │ ├── bark-button.js │ │ │ │ └── bark-message.js │ │ │ └── reducers │ │ │ │ └── dog-reducer.js │ │ ├── server │ │ │ └── index.js │ │ ├── shared │ │ │ └── dog.js │ │ └── test │ │ │ ├── client │ │ │ └── state-test.js │ │ │ └── shared │ │ │ └── dog-test.js │ ├── webpack.config.babel.js │ └── yarn.lock ├── 2-packages │ ├── .gitignore │ ├── README.md │ ├── index.js │ ├── package.json │ └── yarn.lock ├── 3-es6-babel-gulp │ ├── .gitignore │ ├── README.md │ ├── gulpfile.js │ ├── package.json │ ├── src │ │ └── index.js │ └── yarn.lock ├── 4-es6-syntax-class │ ├── .gitignore │ ├── README.md │ ├── gulpfile.js │ ├── package.json │ ├── src │ │ ├── dog.js │ │ └── index.js │ └── yarn.lock ├── 5-es6-modules-syntax │ ├── .gitignore │ ├── README.md │ ├── gulpfile.babel.js │ ├── package.json │ ├── src │ │ ├── dog.js │ │ └── index.js │ └── yarn.lock ├── 6-eslint │ ├── .gitignore │ ├── README.md │ ├── gulpfile.babel.js │ ├── package.json │ ├── src │ │ ├── dog.js │ │ └── index.js │ └── yarn.lock ├── 7-client-webpack │ ├── .gitignore │ ├── README.md │ ├── dist │ │ └── index.html │ ├── gulpfile.babel.js │ ├── package.json │ ├── src │ │ ├── client │ │ │ └── app.js │ │ ├── server │ │ │ └── index.js │ │ └── shared │ │ │ └── dog.js │ ├── webpack.config.babel.js │ └── yarn.lock ├── 8-react │ ├── .gitignore │ ├── README.md │ ├── dist │ │ └── index.html │ ├── gulpfile.babel.js │ ├── package.json │ ├── src │ │ ├── client │ │ │ └── app.jsx │ │ ├── server │ │ │ └── index.js │ │ └── shared │ │ │ └── dog.js │ ├── webpack.config.babel.js │ └── yarn.lock └── 9-redux │ ├── .gitignore │ ├── README.md │ ├── dist │ └── index.html │ ├── gulpfile.babel.js │ ├── package.json │ ├── src │ ├── client │ │ ├── actions │ │ │ └── dog-actions.js │ │ ├── app.jsx │ │ ├── components │ │ │ ├── button.jsx │ │ │ └── message.jsx │ │ ├── containers │ │ │ ├── bark-button.js │ │ │ └── bark-message.js │ │ └── reducers │ │ │ └── dog-reducer.js │ ├── server │ │ └── index.js │ └── shared │ │ └── dog.js │ ├── webpack.config.babel.js │ └── yarn.lock └── yarn.lock /.github/ISSUE_TEMPLATE: -------------------------------------------------------------------------------- 1 | ### Type of issue: (feature suggestion, bug, translation?) 2 | 3 | ### Chapter: 4 | 5 | ### If it's a bug: 6 | 7 | Please try using the code provided in this repository instead of your own to see if that solves the issue. If it does, compare the content of relevant files to see if anything is missing in your version. Every chapter is automatically tested, so issues are likely coming from missing an instruction in the tutorial, a typo, or something like this. Feel free to open an issue if there is a problem with instructions though. 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | "6" 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JavaScript Stack from Scratch [ฉบับแปลไทย] 2 | 3 | [](https://travis-ci.org/verekia/js-stack-from-scratch) [](https://gitter.im/js-stack-from-scratch/Lobby) 4 | 5 | [](https://yarnpkg.com/) 6 | [](https://facebook.github.io/react/) 7 | [](http://gulpjs.com/) 8 | [](http://redux.js.org/) 9 | [](http://eslint.org/) 10 | [](https://webpack.github.io/) 11 | [](https://mochajs.org/) 12 | [](http://chaijs.com/) 13 | [](https://flowtype.org/) 14 | 15 | **หมายเหตุผู้แปล**: Tutorial นี้แปลมาจากต้นฉบับภาษาอังกฤษ [JavaScript Stack from Scratch](https://github.com/verekia/js-stack-from-scratch) โดยส่วนมากจะแปลและแปลงสำนวนจากคำอธิบายดั้งเดิมตรงๆ และอาจมีบางส่วนที่อธิบายเพิ่มเติมขึ้นมาเพื่อให้เห็นภาพชัดเจนมากขึ้น สำหรับใครที่เห็นว่าคำแปลบางส่วนดูแปลกๆ หรืออธิบายเข้าใจยาก สามารถ Pull Request ที่แก้มาได้เลยครับ และน้อมรับคำติชมทุกประการครับผม :) 16 | 17 | ยินดีต้อนรับสู่ Tutorial สอนการสร้าง และใช้งานเครื่องมือต่างๆ ของภาษา JavaScript: **JavaScript Stack from Scratch** 18 | 19 | Tutorial นี้เป็น tutorial ที่จะสอนการใช้งาน JavaScript tool ต่างๆ ด้วยกัน โดยจะสอนแบบตรงไปตรงมา อธิบายทีละจุดทีละเรื่อง ซึ่งผู้อ่านควรจะมีพื้นฐานทางด้านการเขียนโปรแกรมมาบ้าง รวมถึงรู้เบสิคของ JavaScript มาบ้าง **Tutorial นี้จะเน้นไปที่การรวมเอา tools ต่างๆ หลายๆ ตัวมาใช้งานด้วยกัน** และให้**ตัวอย่างโค้ดที่เรียบง่ายที่สุด** สำหรับในแต่ละ tool เพื่อให้นำไปใช้งานต่อได้ง่ายและเข้าใจมากขึ้น ซึ่งคุณเองก็สามารถที่จะอ่าน tutorial นี้ไปแต่ละบท *พร้อมกับสร้าง boilerplate ไว้ใช้งานเองได้ตามที่ต้องการ* 20 | 21 | ในความเป็นจริง คุณไม่จำเป็นต้องใช้ stack แบบที่เรานำเสนอในการพัฒนา Web Page เล็กๆ ที่ไม่จำเป็นต้องมี interaction อะไรมากมาย (ถ้าโปรเจคมีขนาดเล็ก ใช้แค่ Browserify/Webpack + Babel + jQuery ก็เพียงพอแล้วในการที่เราจะเขียนโค้ดตามมาตรฐาน ES6 แต่ก็ใช้ Library เดิมๆ อย่าง jQuery ได้) แต่ถ้าคุณต้องทำ Web Apps ขนาดใหญ่ รองรับต่อการ scale ขนาดของ Web Apps รวมถึงต้องการความช่วยเหลือในการ setup tool ต่่างๆ, Tutorial นี้จะช่วยเหลือคุณได้มาก 22 | 23 | เนื่องจากเป้าหมายของ Tutorial นี้คือจะสอนการใช้ tool หลายๆ ตัวร่วมกัน ซึ่งผมเองก็จะไม่ลงรายละเอียดว่า tools แต่ละตัวนั้นทำงานยังไง ถ้าอยากก็รู้สามารถอ่าน documentation หรือ tutorial แบบ in-depth ตัวอื่นๆ ได้ ถ้าต้องการเข้าใจการทำงานของ tools แต่ละอันมากขึ้น 24 | 25 | ซึ่ง stack ที่จะใช้ในบทนี้นั้นจะใช้ React เป็นหลัก ซึ่งถ้าคุณอยากเรียนรู้ React โปรเจค [create-react-app](https://github.com/facebookincubator/create-react-app) นั้นก็ช่วยให้คุณ set up React environment ได้อย่างง่ายได้ โดยที่คุณไม่ต้องไป config อะไรเองเลย ซึ่งโดยส่วนตัวผมเอง(ผู้เขียน) ก็แนะนำให้ใช้ `create-react-app` สำหรับผู้มาใหม่ที่ต้องใช้ React และต้องเข้าใจเรื่องต่างๆ ให้รวดเร็วหน่อย แต่ใน Tutorial นี้ คุณไม่จำเป็นต้องใช้ config ที่ถูกตั้งค่ามาแล้วเหล่านั้น เพราะเราต้องการให้คุณเข้าใจทุกอย่างตั้งแต่แรกเริ่มไปจนถึงได้ Web Apps ออกมา 26 | 27 | นอกจากนี้เรายังมีตัวอย่างโค้ดให้ด้วยสำหรับแต่ละบท และทุกบทนั้นสามารถ run ได้ด้วยคำสั่งง่ายๆ อย่าง `yarn && yarn start` หรือ `npm install && npm start` ผมเองก็ขอแนะนำให้ทุกท่านเริ่มเขียนทุกอย่างเองตั้งแต่ไม่มีอะไรเลย โดย**ทำตามขั้นตอนแต่ละขั้น** ซึ่งจะอ้างอิงตาม tutorial ในแต่ละบท 28 | 29 | **ทุกบทนั้นจะมีการอ้างถึงและใช้งานโค้ดจากบทก่อนหน้า** ดังนั้นถ้าคุณกำลังมองหา boilerplate ที่มีครบทุกอย่าง ก็ทำได้ง่ายๆ โดยการ clone โค้ดจากบทสุดท้ายมาก็พอแล้ว 30 | 31 | หมายเหตุ: ลำดับการเรียงของบทนั้นไม่ค่อยเหมาะสมกับวิธีการเรียนแบบปกติ เช่น การทำ testing หรือ type checking ควรทำก่อนที่จะเขียน React ด้วยซ้ำ ซึ่งจะเป็นการยากหากเราจะทำการสลับบทเพื่อเรียงลำดับใหม่ เพราะยังมีการปรับแก้แต่ละบทอยู่เรื่อยๆ ซึ่งผม(ผู้เขียน) ขอให้ทุกอย่างเสร็จสิ้นก่อน แล้วเราจะพิจารณาเรื่องนี้อีกทีหนึ่ง 32 | 33 | Code ที่อยู่ใน Tutorial นี้สามารถทำงานได้ทั้งบน Linux, macOS และ Windows 34 | 35 | ## สารบัญ 36 | 37 | [บทที่ 1 - Node, NPM, Yarn และ package.json](/tutorial/1-node-npm-yarn-package-json) 38 | 39 | [บทที่ 2 - ติดตั้งและใช้งาน package](/tutorial/2-packages) 40 | 41 | [บทที่ 3 - ตั้งค่าเพื่อใช้งาน ES6 โดยใช้ Babel และ Gulp](/tutorial/3-es6-babel-gulp) 42 | 43 | [บทที่ 4 - การใช้ ES6 syntax ในการเขียน Class](/tutorial/4-es6-syntax-class) 44 | 45 | [บทที่ 5 - การใช้ ES6 syntax ในการสร้าง modules](/tutorial/5-es6-modules-syntax) 46 | 47 | [บทที่ 6 - การใช้ ESLint](/tutorial/6-eslint) 48 | 49 | [บทที่ 7 - พัฒนาแอพฝั่ง Client โดยใช้ Webpack](/tutorial/7-client-webpack) 50 | 51 | [บทที่ 8 - React](/tutorial/8-react) 52 | 53 | [บทที่ 9 - Redux](/tutorial/9-redux) 54 | 55 | [บทที่ 10 - Immutable JS และ Redux Improvements](/tutorial/10-immutable-redux-improvements) 56 | 57 | [บทที่ 11 - การทำ Testing โดยใช้ Mocha, Chai และ Sinon](/tutorial/11-testing-mocha-chai-sinon) 58 | 59 | [บทที่ 12 - เช็ค Data Type ด้วย Flow](/tutorial/12-flow) 60 | 61 | ## เร็วๆ นี้ 62 | 63 | Production / development environments, Express, React Router, Server-Side Rendering, Styling, Enzyme, Git Hooks. 64 | 65 | ## ภาษาอื่นๆ 66 | 67 | - [中文](https://github.com/pd4d10/js-stack-from-scratch) by [@pd4d10](http://github.com/pd4d10) 68 | - [Italiano](https://github.com/fbertone/js-stack-from-scratch) by [Fabrizio Bertone](https://github.com/fbertone) 69 | - [日本語](https://github.com/takahashim/js-stack-from-scratch) by [@takahashim](https://github.com/takahashim) 70 | - [Русский](https://github.com/UsulPro/js-stack-from-scratch) by [React Theming](https://github.com/sm-react/react-theming) 71 | - [ไทย](https://github.com/MicroBenz/js-stack-from-scratch) by [MicroBenz](https://github.com/MicroBenz) 72 | 73 | If you want to add your translation, please read the [translation recommendations](/how-to-translate.md) to get started! 74 | 75 | ## Credits 76 | 77 | เขียนโดย [@verekia](https://twitter.com/verekia) – [verekia.com](http://verekia.com/). 78 | 79 | ลิขสิทธิ์: MIT 80 | -------------------------------------------------------------------------------- /how-to-translate.md: -------------------------------------------------------------------------------- 1 | # How to translate this tutorial 2 | 3 | Thank you for your interest in translating my tutorial! Here are a few recommendations to get started. 4 | 5 | This tutorial is in constant evolution to provide the best learning experience to readers. Both the code and `README.md` files will change over time. It is great if you do a one-shot translation that won't evolve, but it would be even better if you could try to keep up with the original English version as it changes! 6 | 7 | Here is what I think is a good workflow: 8 | 9 | - Check if there is already a translation issue open for your language. If that's the case, get in touch with the folks who opened it and consider collaborating. All maintainers will be mentioned on the English repo, so team work is encouraged! You can open issues on their translation fork project to offer your help on certain chapters for instance. 10 | 11 | - Join the [Translations Gitter room](https://gitter.im/js-stack-from-scratch/Translations) if you're feeling chatty. 12 | 13 | - Fork the main [English repository](https://github.com/verekia/js-stack-from-scratch). 14 | 15 | - Open an issue on the English repo to show you're currently working on a translation. 16 | 17 | - Translate the `README.md` files. 18 | 19 | - Add a note somewhere explaining on the main `README.md` that this is a translation, with a link to the English repository. If you don't plan to make the translation evolve over time, you can maybe add a little note saying to refer to the English one for an up-to-date version of the tutorial. I'll leave that up to your preference. 20 | 21 | - Submit a Pull Request to the English repo to add a link to your forked repository under the Translations section of the main `README.md`. It could look like this: 22 | 23 | ```md 24 | ## Translations 25 | 26 | - [Language](http://github.com/yourprofile/your-fork) by [You](http://yourwebsite.com) 27 | or 28 | - [Language](http://github.com/yourprofile/your-fork) by [@You](http://twitter.com/yourprofile) 29 | or 30 | - [Language](http://github.com/yourprofile/your-fork) by [@You](http://github.com/yourprofile) 31 | ``` 32 | 33 | Since I want to reward you for your good work as much as possible, you can put any link you like on your name (to your personal website, Twitter profile, or Github profile for instance). 34 | 35 | - After your original one-shot translation, if you want to update your repo with the latest change from the main English repo, [sync your fork](https://help.github.com/articles/syncing-a-fork/) with my repo. To make it easy to see what changed since your initial translation, you can use Github's feature to [compare commits](https://help.github.com/articles/comparing-commits-across-time/#comparing-commits). Set the **base** to the last commit from the English repo you used to translate, and compare it to **master**, like so: 36 | 37 | 38 | https://github.com/verekia/js-stack-from-scratch/compare/dfab78b581a3da800daeb3686b900dd9ea972da0...master 39 | 40 | 41 | That should give you a easy-to-read diff to see exactly what changed in `README.md` files since your translation! 42 | -------------------------------------------------------------------------------- /img/chai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroBenz/js-stack-from-scratch/2b01a4890d67c164b8a7afba508860a63a893b1d/img/chai.png -------------------------------------------------------------------------------- /img/eslint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroBenz/js-stack-from-scratch/2b01a4890d67c164b8a7afba508860a63a893b1d/img/eslint.png -------------------------------------------------------------------------------- /img/flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroBenz/js-stack-from-scratch/2b01a4890d67c164b8a7afba508860a63a893b1d/img/flow.png -------------------------------------------------------------------------------- /img/gulp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroBenz/js-stack-from-scratch/2b01a4890d67c164b8a7afba508860a63a893b1d/img/gulp.png -------------------------------------------------------------------------------- /img/js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroBenz/js-stack-from-scratch/2b01a4890d67c164b8a7afba508860a63a893b1d/img/js.png -------------------------------------------------------------------------------- /img/mocha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroBenz/js-stack-from-scratch/2b01a4890d67c164b8a7afba508860a63a893b1d/img/mocha.png -------------------------------------------------------------------------------- /img/npm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroBenz/js-stack-from-scratch/2b01a4890d67c164b8a7afba508860a63a893b1d/img/npm.png -------------------------------------------------------------------------------- /img/react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroBenz/js-stack-from-scratch/2b01a4890d67c164b8a7afba508860a63a893b1d/img/react.png -------------------------------------------------------------------------------- /img/redux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroBenz/js-stack-from-scratch/2b01a4890d67c164b8a7afba508860a63a893b1d/img/redux.png -------------------------------------------------------------------------------- /img/webpack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroBenz/js-stack-from-scratch/2b01a4890d67c164b8a7afba508860a63a893b1d/img/webpack.png -------------------------------------------------------------------------------- /img/yarn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroBenz/js-stack-from-scratch/2b01a4890d67c164b8a7afba508860a63a893b1d/img/yarn.png -------------------------------------------------------------------------------- /mdlint.js: -------------------------------------------------------------------------------- 1 | const glob = require('glob'); 2 | const markdownlint = require('markdownlint'); 3 | 4 | const config = { 5 | 'default': true, 6 | 'line_length': false, 7 | 'no-emphasis-as-header': false 8 | } 9 | 10 | const files = glob.sync('**/*.md', { ignore: '**/node_modules/**' }); 11 | 12 | markdownlint({ files, config }, (err, result) => { 13 | if (!err) { 14 | const resultString = result.toString(); 15 | console.log('== Linting Markdown Files...'); 16 | if (resultString) { 17 | console.log(resultString); 18 | process.exit(1); 19 | } else { 20 | console.log('== OK!'); 21 | } 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-stack-from-scratch", 3 | "version": "1.0.0", 4 | "description": "JavaScript Stack from Scratch - Step-by-step tutorial to build a modern JavaScript stack", 5 | "scripts": { 6 | "test": "yarn run mdlint && cd tutorial/1-node-npm-yarn-package-json && yarn && yarn run tutorial-test && cd ../2-packages && yarn && yarn run tutorial-test && cd ../3-es6-babel-gulp && yarn && yarn run tutorial-test && cd ../4-es6-syntax-class && yarn && yarn run tutorial-test && cd ../5-es6-modules-syntax && yarn && yarn run tutorial-test && cd ../6-eslint && yarn && yarn run tutorial-test && cd ../7-client-webpack && yarn && yarn run tutorial-test && cd ../8-react && yarn && yarn run tutorial-test && cd ../9-redux && yarn && yarn run tutorial-test && cd ../10-immutable-redux-improvements && yarn && yarn run tutorial-test && cd ../11-testing-mocha-chai-sinon && yarn && yarn run tutorial-test && cd ../12-flow && yarn && yarn run tutorial-test", 7 | "mdlint": "node mdlint.js" 8 | }, 9 | "devDependencies": { 10 | "glob": "^7.1.1", 11 | "markdownlint": "^0.3.0", 12 | "yarn": "^0.16.1" 13 | }, 14 | "repository": "verekia/js-stack-from-scratch", 15 | "author": "Jonathan Verrecchia - @verekia", 16 | "license": "MIT" 17 | } 18 | -------------------------------------------------------------------------------- /tutorial/1-node-npm-yarn-package-json/README.md: -------------------------------------------------------------------------------- 1 | # บทที่ 1 - Node, NPM, Yarn และ package.json 2 | 3 | ในบทนี้เราจะพูดถึงการ set up Node, NPM, Yarn, และการใช้งาน `package.json` ในขั้นต้น 4 | 5 | แรกสุด เราต้องติดตั้ง Node ก่อน ซึ่งเราจะไม่ได้ใช้ Node สำหรับการทำ Back-End ด้วย JavaScript เท่านั้น แต่เครื่องมือที่เราใช้สำหรับ Front-End ก็ใช้ Node ด้วย 6 | 7 | ไปที่หน้า[ดาวน์โหลด](https://nodejs.org/en/download/current/) สำหรับ macOS หรือ Windows แบบ binaries หรือใช้ [package manager](https://nodejs.org/en/download/package-manager/) สำหรับ Linux 8 | 9 | สำหรับ **Ubuntu / Debian** คุณสามารถใช้คำสั่งนี้เพื่อติดตั้ง Node ได้ 10 | 11 | ```bash 12 | curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash - 13 | sudo apt-get install -y nodejs 14 | ``` 15 | 16 | ติดตั้งเวอร์ชันอะไรก็ได้ที่มากกว่า 6.5.0 17 | 18 | `npm` เป็น package manager สำหรับ Node ซึ่งติดตั้งโดยอัตโนมัติอยู่แล้วเมื่อติดตั้ง Node ดังนั้นจึงไม่ต้องติดตั้งอะไรเพิ่ม 19 | 20 | **Note**: ถ้าเคยติดตั้ง Node มาแล้ว ให้ติดตั้ง `nvm` ([Node Version Manager](https://github.com/creationix/nvm)) แล้วใช้ `nvm` ในการติดตั้งเวอร์ชันล่าสุดที่คุณต้องการ 21 | 22 | [Yarn](https://yarnpkg.com/) ก็เป็น package manager เหมือนกับ NPM แต่ว่าเร็วกว่า NPM, ใช้งานแบบ Offline ได้ รวมถึงสามารถค้นหา dependencies ต่างๆ [แบบคาดเดาได้มากขึ้น](https://yarnpkg.com/en/docs/yarn-lock) ตั้งแต่ที่ Yarn [release](https://code.facebook.com/posts/1840075619545360) ออกมาเมื่อตุลาคม 2016 นั้น ก็ได้รับการตอบรับอย่างดี รวมถึงมีการ fix bug ได้รวดเร็วมาก จนกลายเป็น package manager ตัวใหม่ที่เป็นทางเลือกนอกเหนือจากการใช้ NPM ซึ่งใน Tutorial ของเรานั้นจะใช้ Yarn ทั้งหมด แต่ถ้าคุณอยากใช้ NPM เดิมๆ ก็เพียงแค่ใช้ `npm install --save` กับ `npm install --dev` แทน `yarn add` กับ `yarn add --dev` ที่อยู่ใน Tutorial นี้ทั้งหมด 23 | 24 | - ติดตั้ง Yarn โดยทำตาม [instructions](https://yarnpkg.com/en/docs/install) ตามนี้ หรือจะสั่ง `npm install -g yarn` หรือ `sudo npm install -g yarn` ก็ได้ (ใช่แล้ว เราใช้ NPM เพื่อติดตั้ง Yarn มันก็คล้ายๆ กับใช้ Internet Explorer หรือ Safari เพื่อติดตั้ง Chrome นั้นแหละ!) 25 | 26 | - สร้าง folder ใหม่ขึ้นมา (ชื่ออะไรก็ได้) และ `cd` เข้าไป 27 | - สั่ง `yarn init` และตอบคำถามตามที่แสดงมา (หรือใช้ `yarn init -y` เพื่อข้ามทุกคำถามเลยก็ได้) เพื่อให้ Yarn ทำการสร้างไฟล์ `package.json` ขึ้นมาเอง 28 | - สร้าง `index.js` แล้วเขียน `console.log('Hello world')` ในไฟล์นั้นลงไป 29 | - สั่ง `node .` ในโฟลเดอร์ปัจจุบัน (`index.js` คือ default file ที่ Node จะมองหาในโฟลเดอร์ปัจจุบัน) เมื่อสั่งรันแล้วควรจะเห็นคำว่า "Hello world" ขึ้นมา 30 | 31 | ซึ่งการสั่ง `node .` นั้นดูจะ low-level ไปนิด เราจะใช้ NPM/Yarn script เพื่อให้สั่งรันคำสั่งที่ว่านั้นแทน แต่จะได้ความ abstraction และเข้าใจง่ายขึ้นมาด้วย เพราะเราจะสั่งคำสั่งด้วย `yarn start` เฉยๆ เลย แม้ว่าในอนาคตตัวคำสั่งที่เราจะสั่งมันจะซับซ้อนขึ้นไปกว่านี้อีก เราก็สั่ง `yarn start` ก็พอ ซึ่งวิธีการก็ทำตามนี้ 32 | 33 | - ในไฟล์ `package.json` เพิ่ม `scripts` object เข้าไปใน root object แบบนี้ 34 | 35 | ```json 36 | "scripts": { 37 | "start": "node ." 38 | } 39 | ``` 40 | 41 | ซึ่ง `package.json` ต้องเป็นไฟล์ JSON จริงๆ (ห้ามมี trailing commas) ดังนี้ระวังให้ดีเมื่อต้องแก้ไข `package.json` ด้วยมือ 42 | 43 | - สั่ง `yarn start` ทีนี้ก็จะเห็นคำว่า `Hello world` แล้ว 44 | 45 | - สร้าง `.gitignore` ขึ้นมา และเพิ่มข้อมูลเหล่านี้ลงไป 46 | 47 | ```gitignore 48 | npm-debug.log 49 | yarn-error.log 50 | ``` 51 | 52 | **Note**: ถ้าดูในไฟล์ `package.json` ที่เรามีให้ในโปรเจคนี้ จะเห็น script `tutorial-test` ในทุกๆ บทเลย ซึ่ง script นี้จะช่วยให้ผม(ผู้เขียน) เทสว่าโค้ดในบทนี้ใช้งานได้ เมื่อทำการรัน `yarn && yarn start` ดังนั้น คุณสามารถลบ script นี้ทิ้งได้เลยในโปรเจคของคุณเอง 53 | 54 | บทถัดไป [บทที่ 2 - ติดตั้งและใช้งาน package](/tutorial/2-packages) 55 | 56 | กลับไปที่[สารบัญ](https://github.com/MicroBenz/js-stack-from-scratch#table-of-contents) 57 | -------------------------------------------------------------------------------- /tutorial/1-node-npm-yarn-package-json/index.js: -------------------------------------------------------------------------------- 1 | console.log('Hello world'); 2 | -------------------------------------------------------------------------------- /tutorial/1-node-npm-yarn-package-json/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-stack-from-scratch", 3 | "version": "1.0.0", 4 | "description": "JavaScript Stack from Scratch - Step-by-step tutorial to build a modern JavaScript stack", 5 | "scripts": { 6 | "start": "node .", 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "tutorial-test": "yarn start" 9 | }, 10 | "repository": "verekia/js-stack-from-scratch", 11 | "author": "Jonathan Verrecchia - @verekia", 12 | "license": "MIT" 13 | } 14 | -------------------------------------------------------------------------------- /tutorial/10-immutable-redux-improvements/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | yarn-error.log 4 | /lib/ 5 | /dist/client-bundle.js* 6 | -------------------------------------------------------------------------------- /tutorial/10-immutable-redux-improvements/README.md: -------------------------------------------------------------------------------- 1 | # บทที่ 10 - Immutable JS และ Redux Improvements 2 | 3 | ## Immutable JS 4 | 5 | แตกต่างกับบทที่แล้วโดยสิ้นเชิง เนื้อหาในบทนี้จะง่ายขึ้นมาก ซึ่งเกี่ยวข้องกับการใช้งาน Redux ให้มีประสิทธิภาพมากขึ้น 6 | 7 | แรกสุด เราจะทำการเพิ่ม **Immutable JS** เข้าไปในโค้ดเดิมของเรา Immutable นั้นเป็น library ที่ช่วยให้เราจัดการ, แก้ไข object ได้โดยไม่แก้ไข object ตรงๆ เช่น แทนที่จะทำแบบนี้ 8 | 9 | ```javascript 10 | const obj = { a: 1 }; 11 | obj.a = 2; // เปลี่ยนแปลง `obj` ตัวเดิม 12 | ``` 13 | 14 | เราจะทำแบบนี้แทน 15 | 16 | ```javascript 17 | const obj = Immutable.Map({ a: 1 }); 18 | obj.set('a', 2); // ได้ object ก้อนใหม่มา โดยที่ไม่ได้แก้ไขค่าใน `obj` ตัวเดิมตรงๆ 19 | ``` 20 | 21 | วิธีการแบบนี้ทำให้เราสามารถเขียนโปรแกรมในลักษณะของ **functional programming** ได้ ซึ่งจะเป็นประโยชน์มากเมื่อใช้หลักการเขียนโปรแกรมแบบนี้คู่กับ Redux เพราะถ้าดูดีๆ reducer function ของเรานั้น*ต้อง*เป็น pure function ที่ไม่ได้แก้ไข state ที่ถูกส่งมาเป็น parameter ตรงๆ แต่แปลงไปเป็น state object ก้อนใหม่แทน ซึ่งการใช้ Immutable จะช่วยงานเราในส่วนนี้ได้ 22 | 23 | - สั่ง `yarn add immutable` 24 | 25 | เราจะมีการใช้ `Map` ในโปรเจคของเรา แต่ใน ESLint และ Airbnb นั้นจะบ่นเราเรื่องของการใช้ตัวแปรชื่อพิมพ์ใหญ่ทั้งๆ ที่มันไม่ใช่ class ให้เราทำการเพิ่มค่าส่วนนี้เข้าไปใน `package.json` ในส่วนของ `eslintConfig` 26 | 27 | ```json 28 | "rules": { 29 | "new-cap": [ 30 | 2, 31 | { 32 | "capIsNewExceptions": [ 33 | "Map", 34 | "List" 35 | ] 36 | } 37 | ] 38 | } 39 | ``` 40 | 41 | นั่นจะทำให้เราใช้ `Map` กับ `List` (Immutable objects สองชนิดที่เรามักจะใช้แทบจะทุกครั้ง) ได้แล้ว โดยที่ ESLint มองข้ามกฎการตั้งชื่อเริ่มต้นด้วยตัวพิมพ์ใหญ่ไป 42 | 43 | กลับมาที่เรื่องของ Immutable กัน ใน `dog-reducer.js` ให้แก้ไขโค้ดให้มีหน้าตาดังนี้ 44 | 45 | ```javascript 46 | import Immutable from 'immutable'; 47 | import { MAKE_BARK } from '../actions/dog-actions'; 48 | 49 | const initialState = Immutable.Map({ 50 | hasBarked: false, 51 | }); 52 | 53 | const dogReducer = (state = initialState, action) => { 54 | switch (action.type) { 55 | case MAKE_BARK: 56 | return state.set('hasBarked', action.payload); 57 | default: 58 | return state; 59 | } 60 | }; 61 | 62 | export default dogReducer; 63 | ``` 64 | 65 | ตอนนี้จะเห็นว่า state เริ่มต้นจะถูกสร้างมา โดยใช้ Immutable Map แล้ว และ state ใหม่จะถูกสร้างมาโดยใช้ function `set()` เผื่อหลีกเลี่ยงการเปลี่ยนแปลงค่าใน state เก่าโดยตรง 66 | 67 | ในโค้ด `containers/bark-message.js` แก้ function `mapStateToProps` ให้ใช้ `.get('hasBarked')` แทนที่จะใช้ `.hasBarked` 68 | 69 | ```javascript 70 | const mapStateToProps = state => ({ 71 | message: state.dog.get('hasBarked') ? 'The dog barked' : 'The dog did not bark', 72 | }); 73 | ``` 74 | 75 | แอพจะทำงานได้เหมือนเดิม ตามที่เราเคยทำในบทที่แล้ว 76 | 77 | **หมายเหตุ**: ถ้า Babel แจ้งเรื่องเกี่ยวกับ Immutable มีขนาดเกิน 100KB ให้เพิ่ม `"compact": false` ในไฟล์ `package.json` ภายใต้ field `babel` 78 | 79 | ดังที่เห็นใน code ด้านบน state object ของเรายังคงเก็บ เป็น plain object ที่มี `dog` เป็น attribute เหมือนเดิม ซึ่งไม่ใช่ immutable object ซึ่งเป็นเรื่องที่รับได้โดยปกติ แต่ถ้าหากเราต้องการให้ทุกอย่างถูกจัดการด้วยความเป็น immutable objects เท่านั้น คุณต้องใช้ package `redux-immutable` เพื่อแทนที่ function `combineReducers` ของ Redux ไปด้วย 80 | 81 | **ส่วนนี้จะทำหรือไม่ทำก็ได้ แต่หากอยากใช้ `redux-immutable` ก็ให้ทำตามนี้** 82 | 83 | - สั่ง `yarn add redux-immutable` 84 | - แทน function `combineReducers` ในไฟล์ `app.jsx` โดยใช้ package ที่ import มาจาก `redux-immutable` แทน 85 | - ในไฟล์ `bark-message.js` แทน `state.dog.get('hasBarked')` ด้วย `state.getIn(['dog', 'hasBarked'])` 86 | 87 | ## Redux Actions 88 | 89 | เมื่อคุณเริ่มเพิ่ม actions เข้าไปในแอพมากขึ้น เราจะค้นพบว่าเรามักจะทำอะไรซ้ำซากคล้ายๆ เดิมเยอะเหลือเกิน package `redux-actions` ช่วยให้เราลดความซ้ำซากเหล่านั้นได้ ด้วยความช่วยเหลือของ `redux-actions` เราสามารถเขียนโค้ด `dog-actions.js` ให้บางลงได้เยอะ แบบนี้ 90 | 91 | ```javascript 92 | import { createAction } from 'redux-actions'; 93 | 94 | export const MAKE_BARK = 'MAKE_BARK'; 95 | export const makeBark = createAction(MAKE_BARK, () => true); 96 | ``` 97 | 98 | `redux-actions` เป็นหนึ่งในการ implement ของ [Flux Standard Action](https://github.com/acdlite/flux-standard-action) model เหมือนๆ กับ action ที่เราเคยเขียนก่อนหน้านั้น ดังนั้นการใช้ `redux-actions` เหมือนให้เราเขียนโค้ดสั้นลง แต่ได้ผลเหมือนเดิม 99 | 100 | - อย่าลืมที่จะสั่ง `yarn add redux-actions` เข้าไปด้วยก่อนแก้โค้ดนี้ 101 | 102 | บทถัดไป [บทที่ 11 - การทำ Testing โดยใช้ Mocha, Chai และ Sinon](/tutorial/11-testing-mocha-chai-sinon) 103 | 104 | กลับไปยัง[บทที่แล้ว](/tutorial/9-redux) หรือไปที่[สารบัญ](https://github.com/MicroBenz/js-stack-from-scratch#table-of-contents) 105 | -------------------------------------------------------------------------------- /tutorial/10-immutable-redux-improvements/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tutorial/10-immutable-redux-improvements/gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | 3 | import gulp from 'gulp'; 4 | import babel from 'gulp-babel'; 5 | import eslint from 'gulp-eslint'; 6 | import del from 'del'; 7 | import webpack from 'webpack-stream'; 8 | import webpackConfig from './webpack.config.babel'; 9 | 10 | const paths = { 11 | allSrcJs: 'src/**/*.js?(x)', 12 | serverSrcJs: 'src/server/**/*.js?(x)', 13 | sharedSrcJs: 'src/shared/**/*.js?(x)', 14 | clientEntryPoint: 'src/client/app.jsx', 15 | clientBundle: 'dist/client-bundle.js?(.map)', 16 | gulpFile: 'gulpfile.babel.js', 17 | webpackFile: 'webpack.config.babel.js', 18 | libDir: 'lib', 19 | distDir: 'dist', 20 | }; 21 | 22 | gulp.task('lint', () => 23 | gulp.src([ 24 | paths.allSrcJs, 25 | paths.gulpFile, 26 | paths.webpackFile, 27 | ]) 28 | .pipe(eslint()) 29 | .pipe(eslint.format()) 30 | .pipe(eslint.failAfterError()) 31 | ); 32 | 33 | gulp.task('clean', () => del([ 34 | paths.libDir, 35 | paths.clientBundle, 36 | ])); 37 | 38 | gulp.task('build', ['lint', 'clean'], () => 39 | gulp.src(paths.allSrcJs) 40 | .pipe(babel()) 41 | .pipe(gulp.dest(paths.libDir)) 42 | ); 43 | 44 | gulp.task('main', ['lint', 'clean'], () => 45 | gulp.src(paths.clientEntryPoint) 46 | .pipe(webpack(webpackConfig)) 47 | .pipe(gulp.dest(paths.distDir)) 48 | ); 49 | 50 | gulp.task('watch', () => { 51 | gulp.watch(paths.allSrcJs, ['main']); 52 | }); 53 | 54 | gulp.task('default', ['watch', 'main']); 55 | -------------------------------------------------------------------------------- /tutorial/10-immutable-redux-improvements/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-stack-from-scratch", 3 | "version": "1.0.0", 4 | "description": "JavaScript Stack from Scratch - Step-by-step tutorial to build a modern JavaScript stack", 5 | "scripts": { 6 | "start": "gulp", 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "tutorial-test": "gulp main" 9 | }, 10 | "eslintConfig": { 11 | "extends": "airbnb", 12 | "plugins": [ 13 | "import" 14 | ], 15 | "env": { 16 | "browser": true 17 | }, 18 | "rules": { 19 | "new-cap": [ 20 | 2, 21 | { 22 | "capIsNewExceptions": [ 23 | "Map", 24 | "List" 25 | ] 26 | } 27 | ] 28 | } 29 | }, 30 | "babel": { 31 | "presets": [ 32 | "latest", 33 | "react" 34 | ], 35 | "compact": false 36 | }, 37 | "dependencies": { 38 | "babel-polyfill": "^6.16.0", 39 | "immutable": "^3.8.1", 40 | "react": "^15.3.2", 41 | "react-dom": "^15.3.2", 42 | "react-redux": "^4.4.5", 43 | "redux": "^3.6.0", 44 | "redux-actions": "^0.12.0", 45 | "redux-immutable": "^3.0.8" 46 | }, 47 | "devDependencies": { 48 | "babel-loader": "^6.2.5", 49 | "babel-preset-latest": "^6.16.0", 50 | "babel-preset-react": "^6.16.0", 51 | "del": "^2.2.2", 52 | "eslint": "^3.8.1", 53 | "eslint-config-airbnb": "^12.0.0", 54 | "eslint-plugin-import": "^2.0.1", 55 | "eslint-plugin-jsx-a11y": "^2.2.3", 56 | "eslint-plugin-react": "^6.4.1", 57 | "gulp": "^3.9.1", 58 | "gulp-babel": "^6.1.2", 59 | "gulp-eslint": "^3.0.1", 60 | "webpack-stream": "^3.2.0" 61 | }, 62 | "repository": "verekia/js-stack-from-scratch", 63 | "author": "Jonathan Verrecchia - @verekia", 64 | "license": "MIT" 65 | } 66 | -------------------------------------------------------------------------------- /tutorial/10-immutable-redux-improvements/src/client/actions/dog-actions.js: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions'; 2 | 3 | export const MAKE_BARK = 'MAKE_BARK'; 4 | export const makeBark = createAction(MAKE_BARK, () => true); 5 | -------------------------------------------------------------------------------- /tutorial/10-immutable-redux-improvements/src/client/app.jsx: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | 3 | import React from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | import { createStore } from 'redux'; 6 | import { Provider } from 'react-redux'; 7 | import { combineReducers } from 'redux-immutable'; 8 | import dogReducer from './reducers/dog-reducer'; 9 | import BarkMessage from './containers/bark-message'; 10 | import BarkButton from './containers/bark-button'; 11 | 12 | const store = createStore(combineReducers({ 13 | dog: dogReducer, 14 | })); 15 | 16 | ReactDOM.render( 17 |