├── .travis.yml ├── .gitignore ├── img ├── js.png ├── npm.png ├── pm2.png ├── chai.png ├── eslint.png ├── flow.png ├── jest.png ├── mocha.png ├── react.png ├── redux.png ├── yarn.png ├── webpack.png ├── flow-padded.png ├── jest-padded.png ├── js-padded.png ├── pm2-padded.png ├── yarn-padded.png ├── eslint-padded.png ├── pm2-padded-90.png ├── react-padded.png ├── react-router.png ├── redux-padded.png ├── bootstrap-padded.png ├── eslint-padded-90.png ├── flow-padded-90.png ├── jest-padded-90.png ├── react-padded-90.png ├── redux-padded-90.png ├── webpack-padded.png ├── yarn-padded-90.png ├── webpack-padded-90.png ├── bootstrap-padded-90.png ├── react-router-padded.png └── react-router-padded-90.png ├── package.json ├── .github └── ISSUE_TEMPLATE ├── mdlint.js ├── LICENSE.md ├── CHANGELOG.md ├── Definitions.md ├── how-to-translate.md ├── yarn.lock ├── README.md └── tutorial ├── 09-travis-coveralls-heroku.md ├── 07-socket-io.md ├── 01-node-yarn-package-json.md ├── 01-node-yarn-package-json_ru.md ├── 03-express-nodemon-pm2.md ├── 03-express-nodemon-pm2_ru.md ├── 04-webpack-react-hmr.md ├── 04-webpack-react-hmr_ru.md ├── 08-bootstrap-jss.md ├── 02-babel-es6-eslint-flow-jest-husky.md ├── 06-react-router-ssr-helmet.md ├── 06-react-router-ssr-helmet_ru.md └── 05-redux-immutable-fetch.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: node 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | 5 | -------------------------------------------------------------------------------- /img/js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/js.png -------------------------------------------------------------------------------- /img/npm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/npm.png -------------------------------------------------------------------------------- /img/pm2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/pm2.png -------------------------------------------------------------------------------- /img/chai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/chai.png -------------------------------------------------------------------------------- /img/eslint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/eslint.png -------------------------------------------------------------------------------- /img/flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/flow.png -------------------------------------------------------------------------------- /img/jest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/jest.png -------------------------------------------------------------------------------- /img/mocha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/mocha.png -------------------------------------------------------------------------------- /img/react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/react.png -------------------------------------------------------------------------------- /img/redux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/redux.png -------------------------------------------------------------------------------- /img/yarn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/yarn.png -------------------------------------------------------------------------------- /img/webpack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/webpack.png -------------------------------------------------------------------------------- /img/flow-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/flow-padded.png -------------------------------------------------------------------------------- /img/jest-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/jest-padded.png -------------------------------------------------------------------------------- /img/js-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/js-padded.png -------------------------------------------------------------------------------- /img/pm2-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/pm2-padded.png -------------------------------------------------------------------------------- /img/yarn-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/yarn-padded.png -------------------------------------------------------------------------------- /img/eslint-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/eslint-padded.png -------------------------------------------------------------------------------- /img/pm2-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/pm2-padded-90.png -------------------------------------------------------------------------------- /img/react-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/react-padded.png -------------------------------------------------------------------------------- /img/react-router.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/react-router.png -------------------------------------------------------------------------------- /img/redux-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/redux-padded.png -------------------------------------------------------------------------------- /img/bootstrap-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/bootstrap-padded.png -------------------------------------------------------------------------------- /img/eslint-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/eslint-padded-90.png -------------------------------------------------------------------------------- /img/flow-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/flow-padded-90.png -------------------------------------------------------------------------------- /img/jest-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/jest-padded-90.png -------------------------------------------------------------------------------- /img/react-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/react-padded-90.png -------------------------------------------------------------------------------- /img/redux-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/redux-padded-90.png -------------------------------------------------------------------------------- /img/webpack-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/webpack-padded.png -------------------------------------------------------------------------------- /img/yarn-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/yarn-padded-90.png -------------------------------------------------------------------------------- /img/webpack-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/webpack-padded-90.png -------------------------------------------------------------------------------- /img/bootstrap-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/bootstrap-padded-90.png -------------------------------------------------------------------------------- /img/react-router-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/react-router-padded.png -------------------------------------------------------------------------------- /img/react-router-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usulpro/js-stack-from-scratch/HEAD/img/react-router-padded-90.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-stack-from-scratch", 3 | "version": "2.4.5", 4 | "description": "JavaScript Stack from Scratch - Step-by-step tutorial to build a modern JavaScript stack", 5 | "scripts": { 6 | "test": "node mdlint.js" 7 | }, 8 | "devDependencies": { 9 | "glob": "^7.1.1", 10 | "markdownlint": "^0.4.0" 11 | }, 12 | "repository": "verekia/js-stack-from-scratch", 13 | "author": "Jonathan Verrecchia - @verekia", 14 | "license": "MIT" 15 | } 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE: -------------------------------------------------------------------------------- 1 | ### Type of issue: (feature suggestion, bug?) 2 | 3 | ### Chapter: 4 | 5 | ### If it's a bug: 6 | 7 | Please try using the code provided 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 or a typo. Feel free to open an issue if there is a problem with instructions though. 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2017 Jonathan Verrecchia 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 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## v2.4.5 4 | 5 | - Add `babel-plugin-flow-react-proptypes`. 6 | - Add `eslint-plugin-compat`. 7 | - Add JSS `composes` example. 8 | 9 | ## v2.4.4 10 | 11 | - Update Immutable to remove the `import * as Immutable from 'immutable'` syntax. 12 | - Declare Flow types outside of function params for React components. 13 | - Improve Webpack `publicPath`. 14 | 15 | ## V2, up to v2.4.3 16 | 17 | - Gulp is gone, replaced by NPM (Yarn) scripts. 18 | - Express has been added, with template strings for static HTML. Gzip compression enabled. 19 | - Support for development environment with Nodemon and production environment with PM2. 20 | - Minification or sourcemaps depending on the environment via Webpack. 21 | - Add Webpack Dev Server, with Hot Module Replacement and `react-hot-loader`. 22 | - Add an asynchronous call example with `redux-thunk`. 23 | - Linting / type checking / testing is not launched at every file change anymore, but triggered by Git Hooks via Husky. 24 | - Some chapters have been combined to make it easier to maintain the tutorial. 25 | - Replace Chai and Mocha by Jest. 26 | - Add React-Router, Server-Side rendering, `react-helmet`. 27 | - Rename all "dog" things and replaced it by "hello" things. It's a Hello World app after all. 28 | - Add Twitter Bootstrap, JSS, and `react-jss` for styling. 29 | - Add a Websocket example with Socket.IO. 30 | - Add optional Heroku, Travis, and Coveralls integrations. 31 | -------------------------------------------------------------------------------- /Definitions.md: -------------------------------------------------------------------------------- 1 | Термины и определения 2 | 3 | package - пакет 4 | https://ru.wikipedia.org/wiki/Package_(Java) 5 | 6 | class - класс 7 | 8 | Type Checking - Типизация 9 | 10 | OOP - объектно-ориентированное программирование 11 | 12 | JavaScript 13 | 14 | template strings - Шаблонные строки 15 | https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/template_strings 16 | 17 | arrow functions - стрелочные функции 18 | https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Functions/Arrow_functions 19 | 20 | CommonJS 21 | 22 | Gulp - сборщик проектов 23 | https://habrahabr.ru/post/208890/ 24 | 25 | Task Runner - менеджер задач. 26 | (диспетчер запуска задач) 27 | 28 | Node - сервер Node 29 | 30 | plain JavaScript object - простой JavaScript объект 31 | 32 | Babel 33 | 34 | синтаксический сахар - syntactic sugar 35 | 36 | lint - статический анализ, контроль качества кода 37 | https://ru.wikipedia.org/wiki/Lint 38 | 39 | linter - статический анализатор кода. 40 | 41 | Gulpfile 42 | 43 | linting errors 44 | 45 | bundle - сборка 46 | 47 | polyfill 48 | https://remysharp.com/2010/10/08/what-is-a-polyfill 49 | 50 | TRANSPILING - транспилияция (трансляция) 51 | https://www.stevefenton.co.uk/2012/11/compiling-vs-transpiling/ 52 | 53 | tutorial - руководство 54 | 55 | entry point file - файл, указывающий на начальную точку сборки 56 | 57 | back-end - серверная часть 58 | 59 | trailing commas - завершающая запятая (/пробел) 60 | http://www.multitran.ru/c/m.exe?t=6766895_1_2&s1=trailing%20space 61 | 62 | repo/repository - репозиторий 63 | 64 | commit - фиксировать гл. 65 | commit - коммит сущ. 66 | 67 | reducer functions - reducer-функции 68 | -------------------------------------------------------------------------------- /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 an [ongoing translation](https://github.com/verekia/js-stack-from-scratch/issues/147) 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 | - Post in [this issue](https://github.com/verekia/js-stack-from-scratch/issues/147) the language and URL of your forked repo. 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/c65dfa65d02c21063d94f0955de90947ba5273ad...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 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | argparse@^1.0.7: 6 | version "1.0.9" 7 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" 8 | dependencies: 9 | sprintf-js "~1.0.2" 10 | 11 | balanced-match@^0.4.1: 12 | version "0.4.2" 13 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" 14 | 15 | brace-expansion@^1.0.0: 16 | version "1.1.6" 17 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.6.tgz#7197d7eaa9b87e648390ea61fc66c84427420df9" 18 | dependencies: 19 | balanced-match "^0.4.1" 20 | concat-map "0.0.1" 21 | 22 | concat-map@0.0.1: 23 | version "0.0.1" 24 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 25 | 26 | entities@~1.1.1: 27 | version "1.1.1" 28 | resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" 29 | 30 | fs.realpath@^1.0.0: 31 | version "1.0.0" 32 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 33 | 34 | glob@^7.1.1: 35 | version "7.1.1" 36 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" 37 | dependencies: 38 | fs.realpath "^1.0.0" 39 | inflight "^1.0.4" 40 | inherits "2" 41 | minimatch "^3.0.2" 42 | once "^1.3.0" 43 | path-is-absolute "^1.0.0" 44 | 45 | inflight@^1.0.4: 46 | version "1.0.6" 47 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 48 | dependencies: 49 | once "^1.3.0" 50 | wrappy "1" 51 | 52 | inherits@2: 53 | version "2.0.3" 54 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 55 | 56 | linkify-it@^2.0.0: 57 | version "2.0.3" 58 | resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.0.3.tgz#d94a4648f9b1c179d64fa97291268bdb6ce9434f" 59 | dependencies: 60 | uc.micro "^1.0.1" 61 | 62 | markdown-it@^8.3.0: 63 | version "8.3.1" 64 | resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-8.3.1.tgz#2f4b622948ccdc193d66f3ca2d43125ac4ac7323" 65 | dependencies: 66 | argparse "^1.0.7" 67 | entities "~1.1.1" 68 | linkify-it "^2.0.0" 69 | mdurl "^1.0.1" 70 | uc.micro "^1.0.3" 71 | 72 | markdownlint@^0.4.0: 73 | version "0.4.0" 74 | resolved "https://registry.yarnpkg.com/markdownlint/-/markdownlint-0.4.0.tgz#5be8a342c4ab4d5c6cfe8702db30c63bbed01589" 75 | dependencies: 76 | markdown-it "^8.3.0" 77 | 78 | mdurl@^1.0.1: 79 | version "1.0.1" 80 | resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" 81 | 82 | minimatch@^3.0.2: 83 | version "3.0.3" 84 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774" 85 | dependencies: 86 | brace-expansion "^1.0.0" 87 | 88 | once@^1.3.0: 89 | version "1.4.0" 90 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 91 | dependencies: 92 | wrappy "1" 93 | 94 | path-is-absolute@^1.0.0: 95 | version "1.0.1" 96 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 97 | 98 | sprintf-js@~1.0.2: 99 | version "1.0.3" 100 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 101 | 102 | uc.micro@^1.0.1, uc.micro@^1.0.3: 103 | version "1.0.3" 104 | resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.3.tgz#7ed50d5e0f9a9fb0a573379259f2a77458d50192" 105 | 106 | wrappy@1: 107 | version "1.0.2" 108 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JavaScript Stack from Scratch 2 | 3 | [![Build Status](https://travis-ci.org/verekia/js-stack-from-scratch.svg?branch=master)](https://travis-ci.org/verekia/js-stack-from-scratch) [![Join the chat at https://gitter.im/js-stack-from-scratch/Lobby](https://badges.gitter.im/js-stack-from-scratch/Lobby.svg)](https://gitter.im/js-stack-from-scratch/Lobby) 4 | 5 | [![React](/img/react-padded-90.png)](https://facebook.github.io/react/) 6 | [![Redux](/img/redux-padded-90.png)](http://redux.js.org/) 7 | [![React Router](/img/react-router-padded-90.png)](https://github.com/ReactTraining/react-router) 8 | [![Flow](/img/flow-padded-90.png)](https://flowtype.org/) 9 | [![ESLint](/img/eslint-padded-90.png)](http://eslint.org/) 10 | [![Jest](/img/jest-padded-90.png)](https://facebook.github.io/jest/) 11 | [![Yarn](/img/yarn-padded-90.png)](https://yarnpkg.com/) 12 | [![Webpack](/img/webpack-padded-90.png)](https://webpack.github.io/) 13 | [![Bootstrap](/img/bootstrap-padded-90.png)](http://getbootstrap.com/) 14 | 15 | >Это русскоязычная версия руководства Джонатана Верекии ([@verekia](https://twitter.com/verekia)). Оригинальное руководство расположено [здесь](https://github.com/verekia/js-stack-from-scratch). **Начата [работа](https://github.com/UsulPro/js-stack-from-scratch/issues/8) по переводу второй части**. Первая версия находится [тут](https://github.com/UsulPro/js-stack-from-scratch-v1-rus) 16 | 17 | Добро пожаловать в мое современное руководство по стеку технологий JavaScript: **Стек технологий JavaScript с нуля**. 18 | 19 | > 🎉 **Это вторая версия руководства. По сравнению с предыдущм релизом 2016г произведены значительные изменения. См. [Change Log](/CHANGELOG.md)!** 20 | 21 | 22 | Это практико-ориентированное пособие по применению JavaScript технологий. Вам потребуются общие знания по программированию и основы JavaScript. Это пособие **нацелено на интеграцию необходимых инструментов** и предоставляет **максимально простые примеры** для каждого инструмента. Вы можете рассматривать данный документ, как *возможность создать свой собственный шаблонный проект с нуля*. Поскольку целью этого руководства является сборка различных инструментов, я не буду вдаваться в детали по каждому из них. Если вы хотите получить по ним более глубокие знания, изучайте их документацию или другие руководства. 23 | 24 | Конечно, вам не нужны все эти технологии, если вы делаете простую веб страницу с парой JS функций (комбинации Browserify / Webpack + Babel + jQuery достаточно, чтобы написать ES6 код в нескольких файлах), но если вы собираетесь создать масштабируемое веб приложение, и вам нужно все правильно настроить, то это руководство вам отлично подходит. 25 | 26 | В большой части технологий, описываемых здесь, используется React. Если вы только начинаете использовать React и просто хотите изучить его, то [create-react-app](https://github.com/facebookincubator/create-react-app) поможет вам и кратко ознакомит с инфраструктурой React на основе предустановленной конфигурации. Я бы, например, порекомендовал такой подход для тех, кому нужно влиться в команду, использующую React, и на чем-то потренироваться, чтобы подтянуть свои знания. В этом руководстве мы не будем пользоваться предустановленными конфигурациями, поскольку я хочу, чтобы вы полностью понимали все, что происходит "под капотом". 27 | 28 | В каждой части руководства имеются примеры кода, и вы можете запускать их через `yarn && yarn start`. Однако я рекомендую писать все с нуля самостоятельно, следуя **пошаговым инструкциям**. 29 | 30 | Итоговый код данного руководства доступен в отдельном репозитории: [JS-Stack-Boilerplate repository](https://github.com/verekia/js-stack-boilerplate). Он работает под Linux, macOS, и Windows. 31 | 32 | ## Содержание 33 | 34 | [01 - Node, Yarn, `package.json`](/tutorial/01-node-yarn-package-json_ru.md) 35 | 36 | [02 - Babel, ES6, ESLint, Flow, Jest, Husky](/tutorial/02-babel-es6-eslint-flow-jest-husky.md) ![en](https://img.shields.io/badge/ver-en-blue.svg) 37 | 38 | [03 - Express, Nodemon, PM2](/tutorial/03-express-nodemon-pm2_ru.md) 39 | 40 | [04 - Webpack, React, HMR](/tutorial/04-webpack-react-hmr.md) 41 | 42 | [05 - Redux, Immutable, Fetch](/tutorial/05-redux-immutable-fetch_ru.md) 43 | 44 | [06 - React Router, Server-Side Rendering, Helmet](/tutorial/06-react-router-ssr-helmet_ru.md) 45 | 46 | [07 - Socket.IO](/tutorial/07-socket-io.md) ![en](https://img.shields.io/badge/ver-en-blue.svg) 47 | 48 | [08 - Bootstrap, JSS](/tutorial/08-bootstrap-jss.md) ![en](https://img.shields.io/badge/ver-en-blue.svg) 49 | 50 | [09 - Travis, Coveralls, Heroku](/tutorial/09-travis-coveralls-heroku.md) ![en](https://img.shields.io/badge/ver-en-blue.svg) 51 | 52 | ## Далее планируется 53 | 54 | Настройка вашего редактора (Atom и другие), MongoDB, Прогрессивное веб приложение (Progressive Web App). 55 | 56 | ## Переводы на другие языки 57 | 58 | Если вы хотите добавить перевод на другой язык, пожалуйста читайте [рекомендации по переводу](/how-to-translate.md) чтобы начать! 59 | 60 | ### Версия 2 61 | 62 | - [Русский _в процессе превода_](https://github.com/UsulPro/js-stack-from-scratch) by [React Theming](https://github.com/sm-react/react-theming) 63 | 64 | ### Версия 1 65 | 66 | - [中文](https://github.com/pd4d10/js-stack-from-scratch) by [@pd4d10](http://github.com/pd4d10) 67 | - [Italiano](https://github.com/fbertone/js-stack-from-scratch) by [Fabrizio Bertone](https://github.com/fbertone) 68 | - [日本語](https://github.com/takahashim/js-stack-from-scratch) by [@takahashim](https://github.com/takahashim) 69 | - [Русский](https://github.com/UsulPro/js-stack-from-scratch-v1-rus) by [React Theming](https://github.com/sm-react/react-theming) 70 | - [ไทย](https://github.com/MicroBenz/js-stack-from-scratch) by [MicroBenz](https://github.com/MicroBenz) 71 | 72 | ## Сведения 73 | 74 | Создано [@verekia](https://twitter.com/verekia) – [verekia.com](http://verekia.com/). 75 | 76 | Переведено [@usulpro](https://github.com/UsulPro) - [react-theming](https://github.com/sm-react/react-theming) 77 | 78 | Лицензия: MIT 79 | -------------------------------------------------------------------------------- /tutorial/09-travis-coveralls-heroku.md: -------------------------------------------------------------------------------- 1 | # 09 - Travis, Coveralls, and Heroku 2 | 3 | Code for this chapter available in the `master` branch of the [JS-Stack-Boilerplate repository](https://github.com/verekia/js-stack-boilerplate). 4 | 5 | In this chapter, I integrate our app with third-party services. These services offer free and paid plans. It is a bit controversial to use such services in a tutorial that relies exclusively on community-driven and free open source tools, which is why I maintain 2 different branches of the [JS-Stack-Boilerplate repository](https://github.com/verekia/js-stack-boilerplate), `master` and `master-no-services`. 6 | 7 | ## Travis 8 | 9 | > 💡 **[Travis CI](https://travis-ci.org/)** is a popular continuous integration platform, free for open source projects. 10 | 11 | If your project is hosted publicly on Github, integrating Travis is very simple. First, authenticate with your Github account on Travis, and add your repository. 12 | 13 | - Then, create a `.travis.yml` file containing: 14 | 15 | ```yaml 16 | language: node_js 17 | node_js: node 18 | script: yarn test && yarn prod:build 19 | ``` 20 | 21 | Travis will detect automatically that you use Yarn because you have a `yarn.lock` file. Every time you push code to your Github repository, it will run `yarn test && yarn prod:build`. If nothing goes wrong, you should get a green build. 22 | 23 | ## Coveralls 24 | 25 | > 💡 **[Coveralls](https://coveralls.io)** is a service that gives you a history and statistics of your test coverage. 26 | 27 | If your project is open-source on Github and compatible with Coveralls' supported Continuous Integration services, the only thing you need to do is to pipe the coverage file generated by Jest to the `coveralls` binary. 28 | 29 | - Run `yarn add --dev coveralls` 30 | 31 | - Edit your `.travis.yml`'s `script` instruction like so: 32 | 33 | ```yaml 34 | script: yarn test && yarn prod:build && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js 35 | ``` 36 | 37 | Now, every time Travis will build, it will automatically submit your Jest test coverage data to Coveralls. 38 | 39 | ## Badges 40 | 41 | Is everything green on Travis and Coveralls? Great, show it off to the world with shiny badges! 42 | 43 | You can use the code provided by Travis or Coveralls directly, or use [shields.io](http://shields.io/) to homogenize or customize your badges. Let's use shields.io here. 44 | 45 | - Create or edit your `README.md` like so: 46 | 47 | ```md 48 | [![Build Status](https://img.shields.io/travis/GITHUB-USERNAME/GITHUB-REPO.svg?style=flat-square)](https://travis-ci.org/GITHUB-USERNAME/GITHUB-REPO) 49 | [![Coverage Status](https://img.shields.io/coveralls/GITHUB-USERNAME/GITHUB-REPO.svg?style=flat-square)](https://coveralls.io/github/GITHUB-USERNAME/GITHUB-REPO?branch=master) 50 | ``` 51 | 52 | Of course, replace `GITHUB-USERNAME/GITHUB-REPO` by your actual Github username and repository name. 53 | 54 | ## Heroku 55 | 56 | > 💡 **[Heroku](https://www.heroku.com/)** is a [PaaS](https://en.wikipedia.org/wiki/Platform_as_a_service) to deploy to. It takes care of infrastructure details, so you can focus on developing your app without worrying about what happens behind the scenes. 57 | 58 | This tutorial is not sponsored in any way by Heroku, but Heroku being one hell of a platform, I am going to show you how to deploy your app to it. Yep, that's the kind of free love you get when you build a great product. 59 | 60 | **Note**: I might add an AWS section in this chapter later, but one thing at a time. 61 | 62 | ### Web setup 63 | 64 | - If that's not the done yet, install the [Heroku CLI](https://devcenter.heroku.com/articles/getting-started-with-nodejs) and log in. 65 | 66 | - Go to your [Heroku Dashboard](https://dashboard.heroku.com/) and create 2 apps, one called `your-project` and one called `your-project-staging` for instance. 67 | 68 | We are going to let Heroku take care of transpiling our ES6/Flow code with Babel, and generate client bundles with Webpack. But since these are `devDependencies`, Yarn won't install them in a production environment like Heroku. Let's change this behavior with the `NPM_CONFIG_PRODUCTION` env variable. 69 | 70 | - In both apps, under Settings > Config Variables, add `NPM_CONFIG_PRODUCTION` set to `false`. 71 | 72 | - Create a Pipeline, and grant Heroku access to your Github. 73 | 74 | - Add both apps to the pipeline, make the staging one auto-deploy on changes in `master`, and enable Review Apps. 75 | 76 | Alright, let's prepare our project for a deployment to Heroku. 77 | 78 | ### Running in production mode locally 79 | 80 | - Create a `.env` file containing: 81 | 82 | ```.env 83 | NODE_ENV='production' 84 | PORT='8000' 85 | ``` 86 | 87 | That's in this file that you should put your local-only variables and secret variables. Don't commit it to a public repository if your project is private. 88 | 89 | - Add `/.env` to your `.gitignore` 90 | 91 | - Create a `Procfile` file containing: 92 | 93 | ```Procfile 94 | web: node lib/server 95 | ``` 96 | 97 | That's where we specify the entry point of our server. 98 | 99 | We are not going to use PM2 anymore, we'll use `heroku local` instead to run in production mode locally. 100 | 101 | - Run `yarn remove pm2` 102 | 103 | - Edit your `prod:start` script in `package.json`: 104 | 105 | ```json 106 | "prod:start": "heroku local", 107 | ``` 108 | 109 | - Remove `prod:stop` from `package.json`. We don't need it anymore since `heroku local` is a hanging process that we can kill with Ctrl+C, unlike `pm2 start`. 110 | 111 | 🏁 Run `yarn prod:build` and `yarn prod:start`. It should start your server and show you the logs. 112 | 113 | ### Deploying to production 114 | 115 | - Add the following line to your `scripts` in `package.json`: 116 | 117 | ```json 118 | "heroku-postbuild": "yarn prod:build", 119 | ``` 120 | 121 | `heroku-postbuild` is a task that will be run every time you deploy an app to Heroku. 122 | 123 | You will also probably want to specify a specific version of Node or Yarn for Heroku to use. 124 | 125 | - Add this to your `package.json`: 126 | 127 | ```json 128 | "engines": { 129 | "node": "7.x", 130 | "yarn": "0.20.3" 131 | }, 132 | ``` 133 | 134 | - Create an `app.json` file containing: 135 | 136 | ```json 137 | { 138 | "env": { 139 | "NPM_CONFIG_PRODUCTION": "false" 140 | } 141 | } 142 | ``` 143 | 144 | This is for your Review Apps to use. 145 | 146 | You should now be all set to use Heroku Pipeline deployments. 147 | 148 | 🏁 Create a new git branch, make changes and open a Github Pull Request to instantiate a Review App. Check your changes on the Review App URL, and if everything looks good, merge your Pull Request with `master` on Github. A few minutes later, your staging app should have been automatically deployed. Check your changes on the staging app URL, and if everything still looks good, promote staging to production. 149 | 150 | You are done! Congratulations if you finished this entire tutorial starting from scratch. 151 | 152 | You deserve this emoji medal: 🏅 153 | 154 | Back to the [previous section](08-bootstrap-jss.md#readme) or the [table of contents](https://github.com/verekia/js-stack-from-scratch#table-of-contents). 155 | -------------------------------------------------------------------------------- /tutorial/07-socket-io.md: -------------------------------------------------------------------------------- 1 | # 07 - Socket.IO 2 | 3 | Code for this chapter available [here](https://github.com/verekia/js-stack-walkthrough/tree/master/07-socket-io). 4 | 5 | > 💡 **[Socket.IO](https://github.com/socketio/socket.io)** is a library to easily deal with Websockets. It provides a convenient API and fallback for browsers that don't support Websockets. 6 | 7 | In this chapter, we are going to set up a basic message exchange between the client and the server. In order to not add more pages and components – which would be unrelated to the core feature we're interested in here – we are going to make this exchange happen in the browser console. No UI stuff in this chapter. 8 | 9 | - Run `yarn add socket.io socket.io-client` 10 | 11 | ## Server-side 12 | 13 | - Edit your `src/server/index.js` like so: 14 | 15 | ```js 16 | // @flow 17 | 18 | import compression from 'compression' 19 | import express from 'express' 20 | import { Server } from 'http' 21 | import socketIO from 'socket.io' 22 | 23 | import routing from './routing' 24 | import { WEB_PORT, STATIC_PATH } from '../shared/config' 25 | import { isProd } from '../shared/util' 26 | import setUpSocket from './socket' 27 | 28 | const app = express() 29 | // flow-disable-next-line 30 | const http = Server(app) 31 | const io = socketIO(http) 32 | setUpSocket(io) 33 | 34 | app.use(compression()) 35 | app.use(STATIC_PATH, express.static('dist')) 36 | app.use(STATIC_PATH, express.static('public')) 37 | 38 | routing(app) 39 | 40 | http.listen(WEB_PORT, () => { 41 | // eslint-disable-next-line no-console 42 | console.log(`Server running on port ${WEB_PORT} ${isProd ? '(production)' : 43 | '(development).\nKeep "yarn dev:wds" running in an other terminal'}.`) 44 | }) 45 | ``` 46 | 47 | Note that in order for Socket.IO to work, you need to use `Server` from `http` to `listen` to incoming requests, and not the Express `app`. Fortunately, that doesn't change much of the code. All the Websocket details are externalized in a different file, called with `setUpSocket`. 48 | 49 | - Add the following constants to `src/shared/config.js`: 50 | 51 | ```js 52 | export const IO_CONNECT = 'connect' 53 | export const IO_DISCONNECT = 'disconnect' 54 | export const IO_CLIENT_HELLO = 'IO_CLIENT_HELLO' 55 | export const IO_CLIENT_JOIN_ROOM = 'IO_CLIENT_JOIN_ROOM' 56 | export const IO_SERVER_HELLO = 'IO_SERVER_HELLO' 57 | ``` 58 | 59 | These are the *type of messages* your client and your server will exchange. I suggest prefixing them with either `IO_CLIENT` or `IO_SERVER` to make it clearer *who* is sending the message. Otherwise, things can get pretty confusing when you have a lot of message types. 60 | 61 | As you can see, we have a `IO_CLIENT_JOIN_ROOM`, because for the sake of demonstration, we are going to make clients join a room (like a chatroom). Rooms are useful to broadcast messages to specific groups of users. 62 | 63 | - Create a `src/server/socket.js` file containing: 64 | 65 | ```js 66 | // @flow 67 | 68 | import { 69 | IO_CONNECT, 70 | IO_DISCONNECT, 71 | IO_CLIENT_JOIN_ROOM, 72 | IO_CLIENT_HELLO, 73 | IO_SERVER_HELLO, 74 | } from '../shared/config' 75 | 76 | /* eslint-disable no-console */ 77 | const setUpSocket = (io: Object) => { 78 | io.on(IO_CONNECT, (socket) => { 79 | console.log('[socket.io] A client connected.') 80 | 81 | socket.on(IO_CLIENT_JOIN_ROOM, (room) => { 82 | socket.join(room) 83 | console.log(`[socket.io] A client joined room ${room}.`) 84 | 85 | io.emit(IO_SERVER_HELLO, 'Hello everyone!') 86 | io.to(room).emit(IO_SERVER_HELLO, `Hello clients of room ${room}!`) 87 | socket.emit(IO_SERVER_HELLO, 'Hello you!') 88 | }) 89 | 90 | socket.on(IO_CLIENT_HELLO, (clientMessage) => { 91 | console.log(`[socket.io] Client: ${clientMessage}`) 92 | }) 93 | 94 | socket.on(IO_DISCONNECT, () => { 95 | console.log('[socket.io] A client disconnected.') 96 | }) 97 | }) 98 | } 99 | /* eslint-enable no-console */ 100 | 101 | export default setUpSocket 102 | ``` 103 | 104 | Okay, so in this file, we implement *how our server should react when clients connect and send messages to it*: 105 | 106 | - When the client connects, we log it in the server console, and get access to the `socket` object, which we can use to communicate back with that client. 107 | - When a client sends `IO_CLIENT_JOIN_ROOM`, we make it join the `room` it wants. Once it has joined a room, we send 3 demo messages: 1 message to every user, 1 message to users in that room, 1 message to that client only. 108 | - When the client sends `IO_CLIENT_HELLO`, we log its message in the server console. 109 | - When the client disconnects, we log it as well. 110 | 111 | ## Client-side 112 | 113 | The client-side of things is going to look very similar. 114 | 115 | - Edit `src/client/index.jsx` like so: 116 | 117 | ```js 118 | // [...] 119 | import setUpSocket from './socket' 120 | 121 | // [at the very end of the file] 122 | setUpSocket(store) 123 | ``` 124 | 125 | As you can see, we pass the Redux store to `setUpSocket`. This way whenever a Websocket message coming from the server should alter the client's Redux state, we can `dispatch` actions. We are not going to `dispatch` anything in this example though. 126 | 127 | - Create a `src/client/socket.js` file containing: 128 | 129 | ```js 130 | // @flow 131 | 132 | import socketIOClient from 'socket.io-client' 133 | 134 | import { 135 | IO_CONNECT, 136 | IO_DISCONNECT, 137 | IO_CLIENT_HELLO, 138 | IO_CLIENT_JOIN_ROOM, 139 | IO_SERVER_HELLO, 140 | } from '../shared/config' 141 | 142 | const socket = socketIOClient(window.location.host) 143 | 144 | /* eslint-disable no-console */ 145 | // eslint-disable-next-line no-unused-vars 146 | const setUpSocket = (store: Object) => { 147 | socket.on(IO_CONNECT, () => { 148 | console.log('[socket.io] Connected.') 149 | socket.emit(IO_CLIENT_JOIN_ROOM, 'hello-1234') 150 | socket.emit(IO_CLIENT_HELLO, 'Hello!') 151 | }) 152 | 153 | socket.on(IO_SERVER_HELLO, (serverMessage) => { 154 | console.log(`[socket.io] Server: ${serverMessage}`) 155 | }) 156 | 157 | socket.on(IO_DISCONNECT, () => { 158 | console.log('[socket.io] Disconnected.') 159 | }) 160 | } 161 | /* eslint-enable no-console */ 162 | 163 | export default setUpSocket 164 | ``` 165 | 166 | What happens here should not be surprising if you understood well what we did on the server: 167 | 168 | - As soon as the client is connected, we log it in the browser console and join the room `hello-1234` with a `IO_CLIENT_JOIN_ROOM` message. 169 | - We then send `Hello!` with a `IO_CLIENT_HELLO` message. 170 | - If the server sends us a `IO_SERVER_HELLO` message, we log it in the browser console. 171 | - We also log any disconnection. 172 | 173 | 🏁 Run `yarn start` and `yarn dev:wds`, open `http://localhost:8000`. Then, open your browser console, and also look at the terminal of your Express server. You should see the Websocket communication between your client and server. 174 | 175 | Next section: [08 - Bootstrap, JSS](08-bootstrap-jss.md#readme) 176 | 177 | Back to the [previous section](06-react-router-ssr-helmet.md#readme) or the [table of contents](https://github.com/verekia/js-stack-from-scratch#table-of-contents). 178 | -------------------------------------------------------------------------------- /tutorial/01-node-yarn-package-json.md: -------------------------------------------------------------------------------- 1 | # 01 - Node, Yarn, and `package.json` 2 | 3 | Code for this chapter available [here](https://github.com/verekia/js-stack-walkthrough/tree/master/01-node-yarn-package-json). 4 | 5 | In this section we will set up Node, Yarn, a basic `package.json` file, and try a package. 6 | 7 | ## Node 8 | 9 | > 💡 **[Node.js](https://nodejs.org/)** is a JavaScript runtime environment. It is mostly used for Back-End development, but also for general scripting. In the context of Front-End development, it can be used to perform a whole bunch of tasks like linting, testing, and assembling files. 10 | 11 | We will use Node for basically everything in this tutorial, so you're going to need it. Head to the [download page](https://nodejs.org/en/download/current/) for **macOS** or **Windows** binaries, or the [package manager installations page](https://nodejs.org/en/download/package-manager/) for Linux distributions. 12 | 13 | For instance, on **Ubuntu / Debian**, you would run the following commands to install Node: 14 | 15 | ```sh 16 | curl -sL https://deb.nodesource.com/setup_7.x | sudo -E bash - 17 | sudo apt-get install -y nodejs 18 | ``` 19 | 20 | You want any version of Node > 6.5.0. 21 | 22 | ## Node Version Management Tools 23 | 24 | If you need the flexibility to use multiple versions of Node, check out [NVM](https://github.com/creationix/nvm) or [tj/n](https://github.com/tj/n). 25 | 26 | ## NPM 27 | 28 | NPM is the default package manager for Node. It is automatically installed alongside with Node. Package managers are used to install and manage packages (modules of code that you or someone else wrote). We are going to use a lot of packages in this tutorial, but we'll use Yarn, another package manager. 29 | 30 | ## Yarn 31 | 32 | > 💡 **[Yarn](https://yarnpkg.com/)** is a Node.js package manager which is much faster than NPM, has offline support, and fetches dependencies [more predictably](https://yarnpkg.com/en/docs/yarn-lock). 33 | 34 | Since it [came out](https://code.facebook.com/posts/1840075619545360) in October 2016, it received a very quick adoption and may soon become the package manager of choice of the JavaScript community. If you want to stick to NPM you can simply replace all `yarn add` and `yarn add --dev` commands of this tutorial by `npm install --save` and `npm install --save-dev`. 35 | 36 | Install Yarn by following the [instructions](https://yarnpkg.com/en/docs/install) for your OS. I would recommend using the **Installation Script** from the *Alternatives* tab if you are on macOS or Unix, to [avoid](https://github.com/yarnpkg/yarn/issues/1505) relying on other package managers: 37 | 38 | ```sh 39 | curl -o- -L https://yarnpkg.com/install.sh | bash 40 | ``` 41 | 42 | ## `package.json` 43 | 44 | > 💡 **[package.json](https://yarnpkg.com/en/docs/package-json)** is the file used to describe and configure your JavaScript project. It contains general information (your project name, version, contributors, license, etc), configuration options for tools you use, and even a section to run *tasks*. 45 | 46 | - Create a new folder to work in, and `cd` in it. 47 | - Run `yarn init` and answer the questions (`yarn init -y` to skip all questions), to generate a `package.json` file automatically. 48 | 49 | Here is the basic `package.json` I'll use in this tutorial: 50 | 51 | ```json 52 | { 53 | "name": "your-project", 54 | "version": "1.0.0", 55 | "license": "MIT" 56 | } 57 | ``` 58 | 59 | ## Hello World 60 | 61 | - Create an `index.js` file containing `console.log('Hello world')` 62 | 63 | 🏁 Run `node .` in this folder (`index.js` is the default file Node looks for in a folder). It should print "Hello world". 64 | 65 | **Note**: See that 🏁 racing flag emoji? I will use it every time you reach a **checkpoint**. We are sometimes going to make a lot of changes in a row, and your code may not work until you reach the next checkpoint. 66 | 67 | ## `start` script 68 | 69 | Running `node .` to execute our program is a bit too low-level. We are going to use an NPM/Yarn script to trigger the execution of that code instead. That will give us a nice abstraction to be able to always use `yarn start`, even when our program gets more complicated. 70 | 71 | - In `package.json`, add a `scripts` object like so: 72 | 73 | ```json 74 | { 75 | "name": "your-project", 76 | "version": "1.0.0", 77 | "license": "MIT", 78 | "scripts": { 79 | "start": "node ." 80 | } 81 | } 82 | ``` 83 | 84 | `start` is the name we give to the *task* that will run our program. We are going to create a lot of different tasks in this `scripts` object throughout this tutorial. `start` is typically the name given to the default task of an application. Some other standard task names are `stop` and `test`. 85 | 86 | `package.json` must be a valid JSON file, which means that you cannot have trailing commas. So be careful when editing manually your `package.json` file. 87 | 88 | 🏁 Run `yarn start`. It should print `Hello world`. 89 | 90 | ## Git and `.gitignore` 91 | 92 | - Initialize a Git repository with `git init` 93 | 94 | - Create a `.gitignore` file and add the following to it: 95 | 96 | ```gitignore 97 | .DS_Store 98 | /*.log 99 | ``` 100 | 101 | `.DS_Store` files are auto-generated macOS files that you should never have in your repository. 102 | 103 | `npm-debug.log` and `yarn-error.log` are files that are created when your package manager encounters an error, we don't want them versioned in our repository. 104 | 105 | ## Installing and using a package 106 | 107 | In this section we will install and use a package. A "package" is simply a piece of code that someone else wrote, and that you can use in your own code. It can be anything. Here, we're going to try a package that helps you manipulate colors for instance. 108 | 109 | - Install the community-made package called `color` by running `yarn add color` 110 | 111 | Open `package.json` to see how Yarn automatically added `color` in `dependencies`. 112 | 113 | A `node_modules` folder has been created to store the package. 114 | 115 | - Add `node_modules/` to your `.gitignore` 116 | 117 | You will also notice that a `yarn.lock` file got generated by Yarn. You should commit this file to your repository, as it will ensure that everyone in your team uses the same version of your packages. If you're sticking to NPM instead of Yarn, the equivalent of this file is the *shrinkwrap*. 118 | 119 | - Write the following to your `index.js` file: 120 | 121 | ```js 122 | const color = require('color') 123 | 124 | const redHexa = color({ r: 255, g: 0, b: 0 }).hex() 125 | 126 | console.log(redHexa) 127 | ``` 128 | 129 | 🏁 Run `yarn start`. It should print `#FF0000`. 130 | 131 | Congratulations, you installed and used a package! 132 | 133 | `color` is just used in this section to teach you how to use a simple package. We won't need it anymore, so you can uninstall it: 134 | 135 | - Run `yarn remove color` 136 | 137 | ## Two kinds of dependencies 138 | 139 | There are two kinds of package dependencies, `"dependencies"` and `"devDependencies"`: 140 | 141 | **Dependencies** are libraries you need for your application to function (React, Redux, Lodash, jQuery, etc). You install them with `yarn add [package]`. 142 | 143 | **Dev Dependencies** are libraries used during development or to build your application (Webpack, SASS, linters, testing frameworks, etc). You install those with `yarn add --dev [package]`. 144 | 145 | Next section: [02 - Babel, ES6, ESLint, Flow, Jest, Husky](02-babel-es6-eslint-flow-jest-husky.md#readme) 146 | 147 | Back to the [table of contents](https://github.com/verekia/js-stack-from-scratch#table-of-contents). 148 | -------------------------------------------------------------------------------- /tutorial/01-node-yarn-package-json_ru.md: -------------------------------------------------------------------------------- 1 | # 01 - Node, Yarn, и `package.json` 2 | 3 | Код для этой главы доступен [здесь](https://github.com/verekia/js-stack-walkthrough/tree/master/01-node-yarn-package-json). 4 | 5 | В этой части мы настроим Node, Yarn, простой файл `package.json` и протестируем пакет. 6 | 7 | ## Node 8 | 9 | > 💡 **[Node.js](https://nodejs.org/)** - среда исполнения JavaScript, в основном используется для Back-End разработки, но также и для общих целей. В контексте Front-End разработки может применяться для выполнения целого ряда задач, таких как линтинг (linting), тестирование и сборка файлов. 10 | 11 | Мы будем использовать Node буквально везде в этом руководстве, так что вам нужно будет ее установить. Зайдите на [страницу загрузки](https://nodejs.org/en/download/current/) для **macOS** или **Windows** дистрибутивов, или на [страницу установки пакетного менеджера](https://nodejs.org/en/download/package-manager/) для linux. 12 | 13 | Например для **Ubuntu / Debian** выполните следующие команды, чтобы установить Node: 14 | 15 | ```sh 16 | curl -sL https://deb.nodesource.com/setup_7.x | sudo -E bash - 17 | sudo apt-get install -y nodejs 18 | ``` 19 | 20 | Вам подойдет любая версия Node > 6.5.0. 21 | 22 | ## Средства управления версиями Node. 23 | 24 | Если вам нужна гибкость в использовании различных версий Node, рассмотрите [NVM](https://github.com/creationix/nvm) или [tj/n](https://github.com/tj/n). 25 | 26 | ## NPM 27 | 28 | NPM - менеджер пакетов Node по умолчанию. Он автоматически устанавливается вместе с Node. Менеджеры пакетов используются для установки и управления пакетами (модулями кода, которые написали вы или кто-то другой). Мы будем использовать много пакетов в этом руководстве, но мы установим Yarn - другой пакетный менеджер. 29 | 30 | ## Yarn 31 | 32 | > 💡 **[Yarn](https://yarnpkg.com/)** - менеджер пакетов Node.js гораздо более быстрый чем NPM, поддерживает offline режим и с [более предсказуемой](https://yarnpkg.com/en/docs/yarn-lock) загрузкой пакетов. 33 | 34 | С момента [выхода](https://code.facebook.com/posts/1840075619545360) в Октябре 2016 он очень быстро получил признание, и, возможно, скоро станет основным менеджером пакетов для JavaScript сообщества. Если вы хотите остаться с NPM, вы можете просто заменить в этом руководстве все команды `yarn add` и `yarn add --dev` на `npm install --save` and `npm install --save-dev`. 35 | 36 | Установите Yarn, следую [инструкциям](https://yarnpkg.com/en/docs/install) для вашей ОС. Если вы на macOS или Unix, я бы рекомендовал использовать **установочный Script** из вкладки *Alternatives*, чтобы [избежать](https://github.com/yarnpkg/yarn/issues/1505) взаимодействий с другими пакетными менеджерами: 37 | 38 | ```sh 39 | curl -o- -L https://yarnpkg.com/install.sh | bash 40 | ``` 41 | 42 | ## `package.json` 43 | 44 | > 💡 **[package.json](https://yarnpkg.com/en/docs/package-json)** - файл, используемый для описания и конфигурирования вашего JavaScript проекта. Он содержит основную информацию (имя проекта, версия, контрибьюторы, лиценция и т.д.), конфигурационные настройки для инструментов, которые вы используете и даже раздел для запуска *задач*. 45 | 46 | - Создайте новую папку для работы и `cd` в нее. 47 | - Запустите `yarn init` и ответьте на вопросы (`yarn init -y` - пропустить все вопросы), чтобы создать файл `package.json` автоматически. 48 | 49 | Вот простой `package.json` который я буду использовать в этом руководстве: 50 | 51 | ```json 52 | { 53 | "name": "your-project", 54 | "version": "1.0.0", 55 | "license": "MIT" 56 | } 57 | ``` 58 | 59 | ## Hello World 60 | 61 | - Создайте файл `index.js` содержащий `console.log('Hello world')` 62 | 63 | 🏁 Запустите `node .` в этой папке (`index.js` - файл по умолчанию, который Node ищет в папке). Должно выйти "Hello world". 64 | 65 | **Примечание**: Видите этот значек - 🏁 - гоночный флаг? Я буду его использовать каждый раз при достижении **чекпоинта**. Иногда мы будем делать много изменения подряд, и ваш код не будет работать до тех пор, пока вы не достигнете следующего чекпоинта. 66 | 67 | ## `start` script 68 | 69 | Использование `node .` для запуска программ - несколько низкоуровневый подход. Вместо этого, мы будем использовать NPM/Yarn скрипты для запуска выполнения этого кода. Это даст нам отличный уровень абстракции, позволяющий всегда использовать `yarn start`, даже когда наша программа станет более сложной. 70 | 71 | - В `package.json`, добавьте такой объект `scripts`: 72 | 73 | ```json 74 | { 75 | "name": "your-project", 76 | "version": "1.0.0", 77 | "license": "MIT", 78 | "scripts": { 79 | "start": "node ." 80 | } 81 | } 82 | ``` 83 | 84 | `start` - имя, которое мы дали *задаче*, которая будет запускать нашу программу. Мы создадим много различных задач в этом объекте `scripts` в ходе данного руководства. `start` - это имя по умолчанию одной из типичных задач приложения. Другие стандартные названия задач - это `stop` это `test`. 85 | 86 | `package.json` должен быть валидным JSON файлом, что означает, что вы не можете использовать конечные запятые (trailing commas). Поэтому будьте аккуратны вручную редактируя файл `package.json`. 87 | 88 | 🏁 Запустите `yarn start`. Должно выйти `Hello world`. 89 | 90 | ## Git и `.gitignore` 91 | 92 | - Инициализируйте репозиторий Git с помощью `git init` 93 | 94 | - Создайте файл `.gitignore` и добавьте в него следующее: 95 | 96 | ```gitignore 97 | .DS_Store 98 | /*.log 99 | ``` 100 | 101 | `.DS_Store` - автогенерируемые macOS файлы, которые никогда не должны быть в вашем репозитории. 102 | 103 | `npm-debug.log` и `yarn-error.log` - файлы, генерируемые пакетным менеджером при ошибках. Мы не хотим хранить их в репозитории. 104 | 105 | ## Установка и использование пакетов 106 | 107 | В этой части мы установим и воспользуемся пакетом. "Пакет" - это просто кусок кода, который написал кто-то другой и который вы можете использовать в своем собственном коде. Это может быть что угодно. Сейчас, например, мы попробуем пакет, который помогает манипулировать цветами. 108 | 109 | - Установим созданный сообществом пакет, который называется `color`, запустив `yarn add color`. 110 | 111 | Откройте `package.json` чтобы увидеть как Yarn автоматически добавил `color` в `dependencies`. 112 | 113 | Папка `node_modules` была создана для хранения пакетов. 114 | 115 | - Добавьте `node_modules/` в `.gitignore` 116 | 117 | Вы также заметите файл `yarn.lock`, сгенерированный Yarn. Вам нужно добавить коммит с этим файлом в репозиторий, поскольку это даст уверенность, что все в вашей команде используют одни и теже версии пакетов. Если вы предпочитаете NPM а не Yarn, эквивалентом этому файлу будет *shrinkwrap*. 118 | 119 | - Напишите следующее в файл `index.js`: 120 | 121 | ```js 122 | const color = require('color') 123 | 124 | const redHexa = color({ r: 255, g: 0, b: 0 }).hex() 125 | 126 | console.log(redHexa) 127 | ``` 128 | 129 | 🏁 Запустите `yarn start`. Должно выйти `#FF0000`. 130 | 131 | Поздравляем! Вы установили и использовали пакет. 132 | 133 | Мы использовали `color` в этом разделе, чтобы продемонстрировать вам как использовать простой пакет. Он больше нам не потребуется, поэтому можно его удалить: 134 | 135 | - Запустите `yarn remove color` 136 | 137 | ## Два вида зависимостей 138 | 139 | Пакеты могут быть двух видов зависимостей `"dependencies"` и `"devDependencies"` 140 | 141 | **Dependencies** - библиотеки, нужные чтобы ваше приложение функционировало (React, Redux, Lodash, jQuery, etc). Вы устанавливаете их с помощью `yarn add [package]`. 142 | 143 | **Dev Dependencies** - библиотеки, используемые во время разработки или для сборки вашего приложения (Webpack, SASS, linters, testing frameworks, etc). Устанавливайте их с помощью `yarn add --dev [package]`. 144 | 145 | Следующий раздел: [02 - Babel, ES6, ESLint, Flow, Jest, Husky](02-babel-es6-eslint-flow-jest-husky.md#readme) 146 | 147 | Назад в [содержание](https://github.com/verekia/js-stack-from-scratch#table-of-contents). 148 | -------------------------------------------------------------------------------- /tutorial/03-express-nodemon-pm2.md: -------------------------------------------------------------------------------- 1 | # 03 - Express, Nodemon, and PM2 2 | 3 | Code for this chapter available [here](https://github.com/verekia/js-stack-walkthrough/tree/master/03-express-nodemon-pm2). 4 | 5 | In this section we are going to create the server that will render our web app. We will also set up a development mode and a production mode for this server. 6 | 7 | ## Express 8 | 9 | > 💡 **[Express](http://expressjs.com/)** is by far the most popular web application framework for Node. It provides a very simple and minimal API, and its features can be extended with *middleware*. 10 | 11 | Let's set up a minimal Express server to serve an HTML page with some CSS. 12 | 13 | - Delete everything inside `src` 14 | 15 | Create the following files and folders: 16 | 17 | - Create a `public/css/style.css` file containing: 18 | 19 | ```css 20 | body { 21 | width: 960px; 22 | margin: auto; 23 | font-family: sans-serif; 24 | } 25 | 26 | h1 { 27 | color: limegreen; 28 | } 29 | ``` 30 | 31 | - Create an empty `src/client/` folder. 32 | 33 | - Create an empty `src/shared/` folder. 34 | 35 | This folder is where we put *isomorphic / universal* JavaScript code – files that are used by both the client and the server. A great use case of shared code is *routes*, as you will see later in this tutorial when we'll make an asynchronous call. Here we simply have some configuration constants as an example for now. 36 | 37 | - Create a `src/shared/config.js` file, containing: 38 | 39 | ```js 40 | // @flow 41 | 42 | export const WEB_PORT = process.env.PORT || 8000 43 | export const STATIC_PATH = '/static' 44 | export const APP_NAME = 'Hello App' 45 | ``` 46 | 47 | If the Node process used to run your app has a `process.env.PORT` environment variable set (that's the case when you deploy to Heroku for instance), it will use this for the port. If there is none, we default to `8000`. 48 | 49 | - Create a `src/shared/util.js` file containing: 50 | 51 | ```js 52 | // @flow 53 | 54 | // eslint-disable-next-line import/prefer-default-export 55 | export const isProd = process.env.NODE_ENV === 'production' 56 | ``` 57 | 58 | That's a simple util to test if we are running in production mode or not. The `// eslint-disable-next-line import/prefer-default-export` comment is because we only have one named export here. You can remove it as you add other exports in this file. 59 | 60 | - Run `yarn add express compression` 61 | 62 | `compression` is an Express middleware to activate Gzip compression on the server. 63 | 64 | - Create a `src/server/index.js` file containing: 65 | 66 | ```js 67 | // @flow 68 | 69 | import compression from 'compression' 70 | import express from 'express' 71 | 72 | import { APP_NAME, STATIC_PATH, WEB_PORT } from '../shared/config' 73 | import { isProd } from '../shared/util' 74 | import renderApp from './render-app' 75 | 76 | const app = express() 77 | 78 | app.use(compression()) 79 | app.use(STATIC_PATH, express.static('dist')) 80 | app.use(STATIC_PATH, express.static('public')) 81 | 82 | app.get('/', (req, res) => { 83 | res.send(renderApp(APP_NAME)) 84 | }) 85 | 86 | app.listen(WEB_PORT, () => { 87 | // eslint-disable-next-line no-console 88 | console.log(`Server running on port ${WEB_PORT} ${isProd ? '(production)' : '(development)'}.`) 89 | }) 90 | ``` 91 | 92 | Nothing fancy here, it's almost Express' Hello World tutorial with a few additional imports. We're using 2 different static file directories here. `dist` for generated files, `public` for declarative ones. 93 | 94 | - Create a `src/server/render-app.js` file containing: 95 | 96 | ```js 97 | // @flow 98 | 99 | import { STATIC_PATH } from '../shared/config' 100 | 101 | const renderApp = (title: string) => 102 | ` 103 | 104 | 105 | ${title} 106 | 107 | 108 | 109 |

${title}

110 | 111 | 112 | ` 113 | 114 | export default renderApp 115 | ``` 116 | 117 | You know how you typically have *templating engines* on the back-end? Well these are pretty much obsolete now that JavaScript supports template strings. Here we create a function that takes a `title` as a parameter and injects it in both the `title` and `h1` tags of the page, returning the complete HTML string. We also use a `STATIC_PATH` constant as the base path for all our static assets. 118 | 119 | ### HTML template strings syntax highlighting in Atom (optional) 120 | 121 | It might be possible to get syntax highlighting working for HTML code inside template strings depending on your editor. In Atom, if you prefix your template string with an `html` tag (or any tag that *ends* with `html`, like `ilovehtml`), it will automatically highlight the content of that string. I sometimes use the `html` tag of the `common-tags` library to take advantage of this: 122 | 123 | ```js 124 | import { html } from `common-tags` 125 | 126 | const template = html` 127 |
Wow, colors!
128 | ` 129 | ``` 130 | 131 | I did not include this trick in the boilerplate of this tutorial, since it seems to only work in Atom, and it's less than ideal. Some of you Atom users might find it useful though. 132 | 133 | Anyway, back to business! 134 | 135 | - In `package.json` change your `start` script like so: `"start": "babel-node src/server",` 136 | 137 | 🏁 Run `yarn start`, and hit `localhost:8000` in your browser. If everything works as expected you should see a blank page with "Hello App" written both on the tab title and as a green heading on the page. 138 | 139 | **Note**: Some processes – typically processes that wait for things to happen, like a server for instance – will prevent you from entering commands in your terminal until they're done. To interrupt such processes and get your prompt back, press **Ctrl+C**. You can alternatively open a new terminal tab if you want to keep them running while being able to enter commands. You can also make these processes run in the background but that's out of the scope of this tutorial. 140 | 141 | ## Nodemon 142 | 143 | > 💡 **[Nodemon](https://nodemon.io/)** is a utility to automatically restart your Node server when file changes happen in the directory. 144 | 145 | We are going to use Nodemon whenever we are in **development** mode. 146 | 147 | - Run `yarn add --dev nodemon` 148 | 149 | - Change your `scripts` like so: 150 | 151 | ```json 152 | "start": "yarn dev:start", 153 | "dev:start": "nodemon --ignore lib --exec babel-node src/server", 154 | ``` 155 | 156 | `start` is now just a pointer to an other task, `dev:start`. That gives us a layer of abstraction to tweak what the default task is. 157 | 158 | In `dev:start`, the `--ignore lib` flag is to *not* restart the server when changes happen in the `lib` directory. You don't have this directory yet, but we're going to generate it in the next section of this chapter, so it will soon make sense. Nodemon typically runs the `node` binary. In our case, since we're using Babel, we can tell Nodemon to use the `babel-node` binary instead. This way it will understand all the ES6/Flow code. 159 | 160 | 🏁 Run `yarn start` and open `localhost:8000`. Go ahead and change the `APP_NAME` constant in `src/shared/config.js`, which should trigger a restart of your server in the terminal. Refresh the page to see the updated title. Note that this automatic restart of the server is different from *Hot Module Replacement*, which is when components on the page update in real-time. Here we still need a manual refresh, but at least we don't need to kill the process and restart it manually to see changes. Hot Module Replacement will be introduced in the next chapter. 161 | 162 | ## PM2 163 | 164 | > 💡 **[PM2](http://pm2.keymetrics.io/)** is a Process Manager for Node. It keeps your processes alive in production, and offers tons of features to manage them and monitor them. 165 | 166 | We are going to use PM2 whenever we are in **production** mode. 167 | 168 | - Run `yarn add --dev pm2` 169 | 170 | In production, you want your server to be as performant as possible. `babel-node` triggers the whole Babel transpilation process for your files at each execution, which is not something you want in production. We need Babel to do all this work beforehand, and have our server serve plain old pre-compiled ES5 files. 171 | 172 | One of the main features of Babel is to take a folder of ES6 code (usually named `src`) and transpile it into a folder of ES5 code (usually named `lib`). 173 | 174 | This `lib` folder being auto-generated, it's a good practice to clean it up before a new build, since it may contain unwanted old files. A neat simple package to delete files with cross platform support is `rimraf`. 175 | 176 | - Run `yarn add --dev rimraf` 177 | 178 | Let's add the following `prod:build` task to our `package.json`: 179 | 180 | ```json 181 | "prod:build": "rimraf lib && babel src -d lib --ignore .test.js", 182 | ``` 183 | 184 | - Run `yarn prod:build`, and it should generate a `lib` folder containing the transpiled code, except for files ending in `.test.js` (note that `.test.jsx` files are also ignored by this parameter). 185 | 186 | - Add `/lib/` to your `.gitignore` 187 | 188 | One last thing: We are going to pass a `NODE_ENV` environment variable to our PM2 binary. With Unix, you would do this by running `NODE_ENV=production pm2`, but Windows uses a different syntax. We're going to use a small package called `cross-env` to make this syntax work on Windows as well. 189 | 190 | - Run `yarn add --dev cross-env` 191 | 192 | Let's update our `package.json` like so: 193 | 194 | ```json 195 | "scripts": { 196 | "start": "yarn dev:start", 197 | "dev:start": "nodemon --ignore lib --exec babel-node src/server", 198 | "prod:build": "rimraf lib && babel src -d lib --ignore .test.js", 199 | "prod:start": "cross-env NODE_ENV=production pm2 start lib/server && pm2 logs", 200 | "prod:stop": "pm2 delete server", 201 | "test": "eslint src && flow && jest --coverage", 202 | "precommit": "yarn test", 203 | "prepush": "yarn test" 204 | }, 205 | ``` 206 | 207 | 🏁 Run `yarn prod:build`, then run `yarn prod:start`. PM2 should show an active process. Go to `http://localhost:8000/` in your browser and you should see your app. Your terminal should show the logs, which should be "Server running on port 8000 (production).". Note that with PM2, your processes are run in the background. If you press Ctrl+C, it will kill the `pm2 logs` command, which was the last command our our `prod:start` chain, but the server should still render the page. If you want to stop the server, run `yarn prod:stop` 208 | 209 | Now that we have a `prod:build` task, it would be neat to make sure it works fine before pushing code to the repository. Since it is probably unnecessary to run it for every commit, I suggest adding it to the `prepush` task: 210 | 211 | ```json 212 | "prepush": "yarn test && yarn prod:build" 213 | ``` 214 | 215 | 🏁 Run `yarn prepush` or just push your files to trigger the process. 216 | 217 | **Note**: We don't have any test here, so Jest will complain a bit. Ignore it for now. 218 | 219 | Next section: [04 - Webpack, React, HMR](04-webpack-react-hmr.md#readme) 220 | 221 | Back to the [previous section](02-babel-es6-eslint-flow-jest-husky.md#readme) or the [table of contents](https://github.com/verekia/js-stack-from-scratch#table-of-contents). 222 | -------------------------------------------------------------------------------- /tutorial/03-express-nodemon-pm2_ru.md: -------------------------------------------------------------------------------- 1 | # 03 - Express, Nodemon и PM2 2 | 3 | Код для этой главы доступен [тут](https://github.com/verekia/js-stack-walkthrough/tree/master/03-express-nodemon-pm2). 4 | 5 | В этом разделе мы создадим сервер, который будет генерировать наше веб приложение. Также мы настроим для этого сервера режимы разработки и production. 6 | 7 | ## Express 8 | 9 | Express определенно наиболее популярный фреймворк для веб приложений под Node. У него очень простой и минимальный API, а его возможности могут быть расширены с помощью *промежуточного ПО* (middleware). 10 | 11 | Давайте настроим минимальный сервер Express, выдающий HTML страницу с минимальным CSS. 12 | 13 | - Удалите все внутри `src` 14 | 15 | Создайте следующие файлы и папки: 16 | 17 | - Создайте файл `public/css/style.css` содержащий: 18 | 19 | ```css 20 | body { 21 | width: 960px; 22 | margin: auto; 23 | font-family: sans-serif; 24 | } 25 | 26 | h1 { 27 | color: limegreen; 28 | } 29 | ``` 30 | 31 | - Создайте пустую папку `src/client/`. 32 | 33 | - Создайте пустую папку `src/shared/`. 34 | 35 | Эта папка - место в которое мы поместим *изоморфный / универсальный* JavaScript код - файлы которые будут использованы как на клиенте, так и на сервере. Отличный пример использования общего кода - *маршруты* (routes), как вы увидите дальше в этом руководстве, когда мы будем использовать асинхронный вызов. Пока что мы просто разместим тут несколько конфигурационных констант в качестве примера. 36 | 37 | - Создайте файл `src/shared/config.js`, содержащий: 38 | 39 | ```js 40 | // @flow 41 | 42 | export const WEB_PORT = process.env.PORT || 8000 43 | export const STATIC_PATH = '/static' 44 | export const APP_NAME = 'Hello App' 45 | ``` 46 | 47 | Если процесс Node, запускающий ваше приложение содержит переменную окружения `process.env.PORT` (например, в случае, если вы публикуете на Heroku), она будет задавать порт. В противном случае, по умолчанию будет использоваться `8000`. 48 | 49 | - Создайте файл `src/shared/util.js`, содержащий: 50 | 51 | ```js 52 | // @flow 53 | 54 | // eslint-disable-next-line import/prefer-default-export 55 | export const isProd = process.env.NODE_ENV === 'production' 56 | ``` 57 | 58 | Это простая утилита для проверки запущены ли мы в режиме production или нет. Комментарий `// eslint-disable-next-line import/prefer-default-export` нужен изза того, что у нас только один именованный экспорт в это файле. Вы можете его убрать как только добавите сюда экспорт других переменных. 59 | 60 | - Выполните `yarn add express compression` 61 | 62 | `compression` - это промежуточное ПО для Express активирующее Gzip сжатие на сервере. 63 | 64 | - Создайте файл `src/server/index.js` содержащий: 65 | 66 | ```js 67 | // @flow 68 | 69 | import compression from 'compression' 70 | import express from 'express' 71 | 72 | import { APP_NAME, STATIC_PATH, WEB_PORT } from '../shared/config' 73 | import { isProd } from '../shared/util' 74 | import renderApp from './render-app' 75 | 76 | const app = express() 77 | 78 | app.use(compression()) 79 | app.use(STATIC_PATH, express.static('dist')) 80 | app.use(STATIC_PATH, express.static('public')) 81 | 82 | app.get('/', (req, res) => { 83 | res.send(renderApp(APP_NAME)) 84 | }) 85 | 86 | app.listen(WEB_PORT, () => { 87 | // eslint-disable-next-line no-console 88 | console.log(`Server running on port ${WEB_PORT} ${isProd ? '(production)' : '(development)'}.`) 89 | }) 90 | ``` 91 | 92 | Здесь ничего особенного, это практически 'Hello World' для Express плюс несколько дополнительных импортов. Мы здесь используем две отдельных директории для статических файлов. `dist` для генерируемых и `public` для декларируемых. 93 | 94 | - Создайте файл `src/server/render-app.js` содержащий: 95 | 96 | ```js 97 | // @flow 98 | 99 | import { STATIC_PATH } from '../shared/config' 100 | 101 | const renderApp = (title: string) => 102 | ` 103 | 104 | 105 | ${title} 106 | 107 | 108 | 109 |

${title}

110 | 111 | 112 | ` 113 | 114 | export default renderApp 115 | ``` 116 | 117 | Возможно, вам привычно использовать *шаблонизаторы* при работе с back-end? Что ж, теперь их можно считать довольно устаревшимы с тех пор, как JavaScript стал поддерживать шаблонные строки. Здесь мы создали функцию, которая принимает в качестве параметра `title` и вставляет это значение в тэги `title` и `h1`, возвращая строку с полноценной HTML страницей. Мы также используем константу `STATIC_PATH` для задания базового пути для всех наших статических элементов. 118 | 119 | ### Подсветка HTML синтаксиса в шаблонных строках для Atom (не обязательное) 120 | 121 | В зависимости от используемого текстового редактора, возможна подсветка синтаксиса HTML кода внутри шаблонных строк. В Атоме, если вы добавите префикс `html` к шаблонной строке (или любой другой префикс, заканчивающийся на `html`, как `ilovehtml`), то содержимое этой строки автоматически будет подсвечиваться. Я иногда использую `html` тэг из библиотеки `common-tags`, чтобы воспользоваться данной возможностью: 122 | 123 | ```js 124 | import { html } from `common-tags` 125 | 126 | const template = html` 127 |
Wow, colors!
128 | ` 129 | ``` 130 | 131 | Я не стал включать этот трюк в boilerplate этого руководства, поскольку это, похоже работает только в Атоме, и это не идеальный подход. Однако для тех, кто использует Атом, это может быть полезным. 132 | 133 | В любом случае вернемся к нашему проекту. 134 | 135 | - В `package.json` измените скрипт `start` таким образом: `"start": "babel-node src/server",` 136 | 137 | 🏁 Запустите `yarn start` и откройте `localhost:8000` в браузере. Если все заработало как и ожидалось, то вы увидете пустую страницу с надписями "Hello App" в названии вкладки и на зеленом заголовке страницы. 138 | 139 | **Примечание**: Некоторые процессы (обычно процессы, ожидающие своего завершения, как, например, сервер) не позволяют вам вводить команды в терминале пока они не завершатся. Чтобы прервать подобный процесс и получить обратно приглашение к вводу, нажмите **Ctrl+C**. Как вариант, вы можете открыть еще одну вкладку с терминалом, если хотите, чтобы процесс работал, пока вы вводите команды. Вы также можете запустить эти процессы в фоне, но это вне рамок данного руководства. 140 | 141 | ## Nodemon 142 | 143 | > 💡 **[Nodemon](https://nodemon.io/)** - утилита для автоматического перезапуска сервера Node при изменении файлов в директории. 144 | 145 | Мы будем использовать Nodemon в режиме **разработки** 146 | 147 | - Запустите `yarn add --dev nodemon` 148 | 149 | - Измените `scripts` так, чтобы: 150 | 151 | ```json 152 | "start": "yarn dev:start", 153 | "dev:start": "nodemon --ignore lib --exec babel-node src/server", 154 | ``` 155 | 156 | Теперь `start` лишь указатель на другую задачу - `dev:start`. Это дает нам уровень абстракции, позволяющий настраивать какая задача будет выполняться по умолчанию. 157 | 158 | В `dev:start`, мы устанавливаем флаг `--ignore lib` для того, чтобы *не* перезапускать сервер, когда изменения происходят в директории `lib`. У вас пока еще нет этой директории, но мы создадим ее в следующем разделе этой главы. Так что скоро это понадобится. Обычно Nodemon запускает бинарники `node`. В нашем случае, поскольку мы используем Babel, мы, вместо этого, указали Nodemon запускать `babel-node`. Таким образом, мы сделали доступным весь наш ES6/Flow код. 159 | 160 | 🏁 Запустите `yarn start` и откройте `localhost:8000`. Двигаемся дальше и изменим константу `APP_NAME` в `src/shared/config.js`, что должно вызвать перезапуск сервера в терминале. Обновите страницу, чтобы увидеть измененный заголовок. Заметьте, что этот автоматический рестарт сервера отличается от *Hot Module Replacement*, при котором компоненты обновляются на странице в реальном времени. Здесь нам по прежнему требуется ручное обновление, но по крайней мере не нужно убивать процесс и вручную перезапускать сервер, чтобы увидеть изменения. Hot Module Replacement будет представлен в следующей главе. 161 | 162 | ## PM2 163 | 164 | > 💡 **[PM2](http://pm2.keymetrics.io/)** - это менеджер процессов для Node, обеспечивающий жизнеспособность вашего приложения и предлагающий тонны возможностей по управлению и мониторингу. 165 | 166 | Мы будем использовать PM2 в режиме **production** 167 | 168 | 169 | - Выполните `yarn add --dev pm2` 170 | 171 | В production вы хотите, чтобы сервер был настолько производительным, насколько это возможно. `babel-node` начинает процесс транспиляции всех файлов при каждом перезапуске, чего вы бы хотели избежать в production. Нам нужно, чтобы Babel выполнил всю эту работу заранее, и сервер выдавал обычные старые предкомпилированные ES5 файлы. 172 | 173 | Одной из основных возможностей Babel является способность взять папку с ES6 кодом (обычно `src`) и транспилировать его в папку с ES5 кодом (обычно `lib`). 174 | 175 | Поскольку папка `lib` автогенерируется, хорошей практикой будет очищать ее перед каждым новым построением, поскольку она может содержать нежелательные старые файлы. `rimraf` – простой лаконичный пакет, для удаления файлов с кроссплатформенной поддержкой. 176 | 177 | - Запустите `yarn add --dev rimraf` 178 | 179 | Давайте добавим следующую задачу `prod:build` в `package.json`: 180 | 181 | ```json 182 | "prod:build": "rimraf lib && babel src -d lib --ignore .test.js", 183 | ``` 184 | 185 | - Запустите `yarn prod:build`. Это должно сгенерировать папку `lib`, содержащую транспилированный код, за исключением файлов, заканчивающихся на `.test.js` (заметьте, что файлы `.test.jsx` также игнорируются с помощью этого параметра). 186 | 187 | - Добавьте `/lib/` в `.gitignore` 188 | 189 | Последняя вещь: Мы собираемся передать переменную окружения `NODE_ENV` в исполняемый файл PM2. В Unix, вы бы сделали это через `NODE_ENV=production pm2`, но Windows использует другой синтаксис. Мы воспользуемся небольшим пакетом `cross-env`, чтобы заставить этот синтаксис работать также и для Windows. 190 | 191 | - Запустите `yarn add --dev cross-env` 192 | 193 | Обновим `package.json` так: 194 | 195 | ```json 196 | "scripts": { 197 | "start": "yarn dev:start", 198 | "dev:start": "nodemon --ignore lib --exec babel-node src/server", 199 | "prod:build": "rimraf lib && babel src -d lib --ignore .test.js", 200 | "prod:start": "cross-env NODE_ENV=production pm2 start lib/server && pm2 logs", 201 | "prod:stop": "pm2 delete server", 202 | "test": "eslint src && flow && jest --coverage", 203 | "precommit": "yarn test", 204 | "prepush": "yarn test" 205 | }, 206 | ``` 207 | 208 | 🏁 Запустите `yarn prod:build`, а затем `yarn prod:start`. PM2 должен показать активный процесс. Зайдите на `http://localhost:8000/` в браузере и вы должны увидеть наше приложение. Терминал должен выдать лог: "Server running on port 8000 (production).". Заметьте, что PM2 запускает процессы в фоне. Если вы нажмете Ctrl+C, это прервет команду `pm2 logs`, которая была последней в цепочке после `prod:start`, но сам сервер по прежнему должен генерировать страницы. Если вам нужно остановить сервер, наберите `yarn prod:stop`. 209 | 210 | Теперь, когда у нас есть задача `prod:build`, было бы здорово проверять все ли работает хорошо перед тем как закачивать код в репозиторий. Поскольку, возможно не требуется запускать его перед каждым коммитом, я предлагаю добавить это в задачу `prepush`: 211 | 212 | ```json 213 | "prepush": "yarn test && yarn prod:build" 214 | ``` 215 | 216 | 🏁 Запустите `yarn prepush` или просто начните загружать файлы (push), чтобы запустить этот процесс. 217 | 218 | **Примечание**: У нас пока нет никаких тестов, так что Jest пожалуется на это. Пока что проигнорируйте это. 219 | 220 | Следующий раздел: [04 - Webpack, React, HMR](04-webpack-react-hmr_ru.md) 221 | 222 | Назад в [предыдущий раздел](02-babel-es6-eslint-flow-jest-husky_ru.md) или [содержание](../..). -------------------------------------------------------------------------------- /tutorial/04-webpack-react-hmr.md: -------------------------------------------------------------------------------- 1 | # 04 - Webpack, React, and Hot Module Replacement 2 | 3 | Code for this chapter available [here](https://github.com/verekia/js-stack-walkthrough/tree/master/04-webpack-react-hmr). 4 | 5 | ## Webpack 6 | 7 | > 💡 **[Webpack](https://webpack.js.org/)** is a *module bundler*. It takes a whole bunch of various source files, processes them, and assembles them into one (usually) JavaScript file called a bundle, which is the only file your client will execute. 8 | 9 | Let's create some very basic *hello world* and bundle it with Webpack. 10 | 11 | - In `src/shared/config.js`, add the following constants: 12 | 13 | ```js 14 | export const WDS_PORT = 7000 15 | 16 | export const APP_CONTAINER_CLASS = 'js-app' 17 | export const APP_CONTAINER_SELECTOR = `.${APP_CONTAINER_CLASS}` 18 | ``` 19 | 20 | - Create an `src/client/index.js` file containing: 21 | 22 | ```js 23 | import 'babel-polyfill' 24 | 25 | import { APP_CONTAINER_SELECTOR } from '../shared/config' 26 | 27 | document.querySelector(APP_CONTAINER_SELECTOR).innerHTML = '

Hello Webpack!

' 28 | ``` 29 | 30 | If you want to use some of the most recent ES features in your client code, like `Promise`s, you need to include the [Babel Polyfill](https://babeljs.io/docs/usage/polyfill/) before anything else in your bundle. 31 | 32 | - Run `yarn add babel-polyfill` 33 | 34 | If you run ESLint on this file, it will complain about `document` being undefined. 35 | 36 | - Add the following to `env` in your `.eslintrc.json` to allow the use of `window` and `document`: 37 | 38 | ```json 39 | "env": { 40 | "browser": true, 41 | "jest": true 42 | } 43 | ``` 44 | 45 | Alright, we now need to bundle this ES6 client app into an ES5 bundle. 46 | 47 | - Create a `webpack.config.babel.js` file containing: 48 | 49 | ```js 50 | // @flow 51 | 52 | import path from 'path' 53 | 54 | import { WDS_PORT } from './src/shared/config' 55 | import { isProd } from './src/shared/util' 56 | 57 | export default { 58 | entry: [ 59 | './src/client', 60 | ], 61 | output: { 62 | filename: 'js/bundle.js', 63 | path: path.resolve(__dirname, 'dist'), 64 | publicPath: isProd ? '/static/' : `http://localhost:${WDS_PORT}/dist/`, 65 | }, 66 | module: { 67 | rules: [ 68 | { test: /\.(js|jsx)$/, use: 'babel-loader', exclude: /node_modules/ }, 69 | ], 70 | }, 71 | devtool: isProd ? false : 'source-map', 72 | resolve: { 73 | extensions: ['.js', '.jsx'], 74 | }, 75 | devServer: { 76 | port: WDS_PORT, 77 | }, 78 | } 79 | ``` 80 | 81 | This file is used to describe how our bundle should be assembled: `entry` is the starting point of our app, `output.filename` is the name of the bundle to generate, `output.path` and `output.publicPath` describe the destination folder and URL. We put the bundle in a `dist` folder, which will contain things that are generated automatically (unlike the declarative CSS we created earlier which lives in `public`). `module.rules` is where you tell Webpack to apply some treatment to some type of files. Here we say that we want all `.js` and `.jsx` (for React) files except the ones in `node_modules` to go through something called `babel-loader`. We also want these two extensions to be used to `resolve` modules when we `import` them. Finally, we declare a port for Webpack Dev Server. 82 | 83 | **Note**: The `.babel.js` extension is a Webpack feature to apply our Babel transformations to this config file. 84 | 85 | `babel-loader` is a plugin for Webpack that transpiles your code just like we've been doing since the beginning of this tutorial. The only difference is that this time, the code will end up running in the browser instead of your server. 86 | 87 | - Run `yarn add --dev webpack webpack-dev-server babel-core babel-loader` 88 | 89 | `babel-core` is a peer-dependency of `babel-loader`, so we installed it as well. 90 | 91 | - Add `/dist/` to your `.gitignore` 92 | 93 | ### Tasks update 94 | 95 | In development mode, we are going to use `webpack-dev-server` to take advantage of Hot Module Reloading (later in this chapter), and in production we'll simply use `webpack` to generate bundles. In both cases, the `--progress` flag is useful to display additional information when Webpack is compiling your files. In production, we'll also pass the `-p` flag to `webpack` to minify our code, and the `NODE_ENV` variable set to `production`. 96 | 97 | Let's update our `scripts` to implement all this, and improve some other tasks as well: 98 | 99 | ```json 100 | "scripts": { 101 | "start": "yarn dev:start", 102 | "dev:start": "nodemon -e js,jsx --ignore lib --ignore dist --exec babel-node src/server", 103 | "dev:wds": "webpack-dev-server --progress", 104 | "prod:build": "rimraf lib dist && babel src -d lib --ignore .test.js && cross-env NODE_ENV=production webpack -p --progress", 105 | "prod:start": "cross-env NODE_ENV=production pm2 start lib/server && pm2 logs", 106 | "prod:stop": "pm2 delete server", 107 | "lint": "eslint src webpack.config.babel.js --ext .js,.jsx", 108 | "test": "yarn lint && flow && jest --coverage", 109 | "precommit": "yarn test", 110 | "prepush": "yarn test && yarn prod:build" 111 | }, 112 | ``` 113 | 114 | In `dev:start` we explicitly declare file extensions to monitor, `.js` and `.jsx`, and add `dist` in the ignored directories. 115 | 116 | We created a separate `lint` task and added `webpack.config.babel.js` to the files to lint. 117 | 118 | - Next, let's create the container for our app in `src/server/render-app.js`, and include the bundle that will be generated: 119 | 120 | ```js 121 | // @flow 122 | 123 | import { APP_CONTAINER_CLASS, STATIC_PATH, WDS_PORT } from '../shared/config' 124 | import { isProd } from '../shared/util' 125 | 126 | const renderApp = (title: string) => 127 | ` 128 | 129 | 130 | ${title} 131 | 132 | 133 | 134 |
135 | 136 | 137 | 138 | ` 139 | 140 | export default renderApp 141 | ``` 142 | 143 | Depending on the environment we're in, we'll include either the Webpack Dev Server bundle, or the production bundle. Note that the path to Webpack Dev Server's bundle is *virtual*, `dist/js/bundle.js` is not actually read from your hard drive in development mode. It's also necessary to give Webpack Dev Server a different port than your main web port. 144 | 145 | - Finally, in `src/server/index.js`, tweak your `console.log` message like so: 146 | 147 | ```js 148 | console.log(`Server running on port ${WEB_PORT} ${isProd ? '(production)' : 149 | '(development).\nKeep "yarn dev:wds" running in an other terminal'}.`) 150 | ``` 151 | 152 | That will give other developers a hint about what to do if they try to just run `yarn start` without Webpack Dev Server. 153 | 154 | Alright that was a lot of changes, let's see if everything works as expected: 155 | 156 | 🏁 Run `yarn start` in a terminal. Open an other terminal tab or window, and run `yarn dev:wds` in it. Once Webpack Dev Server is done generating the bundle and its sourcemaps (which should both be ~600kB files) and both processes hang in your terminals, open `http://localhost:8000/` and you should see "Hello Webpack!". Open your Chrome console, and under the Source tab, check which files are included. You should only see `static/css/style.css` under `localhost:8000/`, and have all your ES6 source files under `webpack://./src`. That means sourcemaps are working. In your editor, in `src/client/index.js`, try changing `Hello Webpack!` into any other string. As you save the file, Webpack Dev Server in your terminal should generate a new bundle and the Chrome tab should reload automatically. 157 | 158 | - Kill the previous processes in your terminals with Ctrl+C, then run `yarn prod:build`, and then `yarn prod:start`. Open `http://localhost:8000/` and you should still see "Hello Webpack!". In the Source tab of the Chrome console, you should this time find `static/js/bundle.js` under `localhost:8000/`, but no `webpack://` sources. Click on `bundle.js` to make sure it is minified. Run `yarn prod:stop`. 159 | 160 | Good job, I know this was quite dense. You deserve a break! The next section is easier. 161 | 162 | **Note**: I would recommend to have at least 3 terminals open, one for your Express server, one for the Webpack Dev Server, and one for Git, tests, and general commands like installing packages with `yarn`. Ideally, you should split your terminal screen in multiple panes to see them all. 163 | 164 | ## React 165 | 166 | > 💡 **[React](https://facebook.github.io/react/)** is a library for building user interfaces by Facebook. It uses the **[JSX](https://facebook.github.io/react/docs/jsx-in-depth.html)** syntax to represent HTML elements and components while leveraging the power of JavaScript. 167 | 168 | In this section we are going to render some text using React and JSX. 169 | 170 | First, let's install React and ReactDOM: 171 | 172 | - Run `yarn add react react-dom` 173 | 174 | Rename your `src/client/index.js` file into `src/client/index.jsx` and write some React code in it: 175 | 176 | ```js 177 | // @flow 178 | 179 | import 'babel-polyfill' 180 | 181 | import React from 'react' 182 | import ReactDOM from 'react-dom' 183 | 184 | import App from './app' 185 | import { APP_CONTAINER_SELECTOR } from '../shared/config' 186 | 187 | ReactDOM.render(, document.querySelector(APP_CONTAINER_SELECTOR)) 188 | ``` 189 | 190 | - Create a `src/client/app.jsx` file containing: 191 | 192 | ```js 193 | // @flow 194 | 195 | import React from 'react' 196 | 197 | const App = () =>

Hello React!

198 | 199 | export default App 200 | ``` 201 | 202 | Since we use the JSX syntax here, we have to tell Babel that it needs to transform it with the `babel-preset-react` preset. And while we're at it, we're also going to add a Babel plugin called `flow-react-proptypes` which automatically generates PropTypes from Flow annotations for your React components. 203 | 204 | - Run `yarn add --dev babel-preset-react babel-plugin-flow-react-proptypes` and edit your `.babelrc` file like so: 205 | 206 | ```json 207 | { 208 | "presets": [ 209 | "env", 210 | "flow", 211 | "react" 212 | ], 213 | "plugins": [ 214 | "flow-react-proptypes" 215 | ] 216 | } 217 | ``` 218 | 219 | 🏁 Run `yarn start` and `yarn dev:wds` and hit `http://localhost:8000`. You should see "Hello React!". 220 | 221 | Now try changing the text in `src/client/app.jsx` to something else. Webpack Dev Server should reload the page automatically, which is pretty neat, but we are going to make it even better. 222 | 223 | ## Hot Module Replacement 224 | 225 | > 💡 **[Hot Module Replacement](https://webpack.js.org/concepts/hot-module-replacement/)** (*HMR*) is a powerful Webpack feature to replace a module on the fly without reloading the entire page. 226 | 227 | To make HMR work with React, we are going to need to tweak a few things. 228 | 229 | - Run `yarn add react-hot-loader@next` 230 | 231 | - Edit your `webpack.config.babel.js` like so: 232 | 233 | ```js 234 | import webpack from 'webpack' 235 | // [...] 236 | entry: [ 237 | 'react-hot-loader/patch', 238 | './src/client', 239 | ], 240 | // [...] 241 | devServer: { 242 | port: WDS_PORT, 243 | hot: true, 244 | }, 245 | plugins: [ 246 | new webpack.optimize.OccurrenceOrderPlugin(), 247 | new webpack.HotModuleReplacementPlugin(), 248 | new webpack.NamedModulesPlugin(), 249 | new webpack.NoEmitOnErrorsPlugin(), 250 | ], 251 | ``` 252 | 253 | - Edit your `src/client/index.jsx` file: 254 | 255 | ```js 256 | // @flow 257 | 258 | import 'babel-polyfill' 259 | 260 | import React from 'react' 261 | import ReactDOM from 'react-dom' 262 | import { AppContainer } from 'react-hot-loader' 263 | 264 | import App from './app' 265 | import { APP_CONTAINER_SELECTOR } from '../shared/config' 266 | 267 | const rootEl = document.querySelector(APP_CONTAINER_SELECTOR) 268 | 269 | const wrapApp = AppComponent => 270 | 271 | 272 | 273 | 274 | ReactDOM.render(wrapApp(App), rootEl) 275 | 276 | if (module.hot) { 277 | // flow-disable-next-line 278 | module.hot.accept('./app', () => { 279 | // eslint-disable-next-line global-require 280 | const NextApp = require('./app').default 281 | ReactDOM.render(wrapApp(NextApp), rootEl) 282 | }) 283 | } 284 | ``` 285 | 286 | We need to make our `App` a child of `react-hot-loader`'s `AppContainer`, and we need to `require` the next version of our `App` when hot-reloading. To make this process clean and DRY, we create a little `wrapApp` function that we use in both places it needs to render `App`. Feel free to move the `eslint-disable global-require` to the top of the file to make this more readable. 287 | 288 | 🏁 Restart your `yarn dev:wds` process if it was still running. Open `localhost:8000`. In the Console tab, you should see some logs about HMR. Go ahead and change something in `src/client/app.jsx` and your changes should be reflected in your browser after a few seconds, without any full-page reload! 289 | 290 | Next section: [05 - Redux, Immutable, Fetch](05-redux-immutable-fetch.md#readme) 291 | 292 | Back to the [previous section](03-express-nodemon-pm2.md#readme) or the [table of contents](https://github.com/verekia/js-stack-from-scratch#table-of-contents). -------------------------------------------------------------------------------- /tutorial/04-webpack-react-hmr_ru.md: -------------------------------------------------------------------------------- 1 | # 04 - Webpack, React и Hot Module Replacement 2 | 3 | Код для этой главы доступен [здесь](https://github.com/verekia/js-stack-walkthrough/tree/master/04-webpack-react-hmr). 4 | 5 | ## Webpack 6 | 7 | > 💡 **[Webpack](https://webpack.js.org/)** - *сборщик модулей*. Он берет все возможные исходные файлы, обрабатывает их и собирает в один (обычно) JavaScript файл, называемый сборкой, и это будет единственный файл исполняемый на клиенте. 8 | 9 | Давайте создадим какой-нибудь простой *hello world* и соберем его с помощью Webpack. 10 | 11 | - В `src/shared/config.js` добавьте следующие константы: 12 | 13 | ```js 14 | export const WDS_PORT = 7000 15 | 16 | export const APP_CONTAINER_CLASS = 'js-app' 17 | export const APP_CONTAINER_SELECTOR = `.${APP_CONTAINER_CLASS}` 18 | ``` 19 | 20 | - Создайте файл `src/client/index.js`, содержащий: 21 | 22 | ```js 23 | import 'babel-polyfill' 24 | 25 | import { APP_CONTAINER_SELECTOR } from '../shared/config' 26 | 27 | document.querySelector(APP_CONTAINER_SELECTOR).innerHTML = '

Hello Webpack!

' 28 | ``` 29 | 30 | Если вы хотите использовать новейшие возможности ES в клиентском коде, такие как `Promise`, то вам нужно включить [Babel Polyfill](https://babeljs.io/docs/usage/polyfill/) до какого-либо другого кода в сборке. 31 | 32 | - Запустите `yarn add babel-polyfill` 33 | 34 | Если вы запустите ESLint на этом файле, он будет жаловаться, что `document` undefined. 35 | 36 | - Добавьте раздел `env` в `.eslintrc.json`, чтобы позволить использование `window` и `document`: 37 | 38 | ```json 39 | "env": { 40 | "browser": true, 41 | "jest": true 42 | } 43 | ``` 44 | 45 | Хорошо, теперь нам нужно собрать это клиентское ES6 приложение в ES5 сборку. 46 | 47 | - Создайте файл `webpack.config.babel.js` содержащий: 48 | 49 | ```js 50 | // @flow 51 | 52 | import path from 'path' 53 | 54 | import { WDS_PORT } from './src/shared/config' 55 | import { isProd } from './src/shared/util' 56 | 57 | export default { 58 | entry: [ 59 | './src/client', 60 | ], 61 | output: { 62 | filename: 'js/bundle.js', 63 | path: path.resolve(__dirname, 'dist'), 64 | publicPath: isProd ? '/static/' : `http://localhost:${WDS_PORT}/dist/`, 65 | }, 66 | module: { 67 | rules: [ 68 | { test: /\.(js|jsx)$/, use: 'babel-loader', exclude: /node_modules/ }, 69 | ], 70 | }, 71 | devtool: isProd ? false : 'source-map', 72 | resolve: { 73 | extensions: ['.js', '.jsx'], 74 | }, 75 | devServer: { 76 | port: WDS_PORT, 77 | }, 78 | } 79 | ``` 80 | 81 | Этот файл используется для описания того, как должна быть устроена наша сборка: `entry` - стартовая точка нашего приложения, `output.filename` - имя генерируемой сборки, `output.path` и `output.publicPath` описывают путь до папки со сборкой и URL. Мы поместим сборку в папку `dist`, которая будет содержать автоматически генерируемые вещи (в отличие от обитающих в `public` декларативных CSS, которые мы создавали до этого). В `module.rules` мы сообщаем Webpack к каким типам файлов применять какие обработчики. Здесь мы говорим, что хотим пропускать все `.js` и `.jsx` (для реакта) файлы через нечто, называемое `babel-loader`, за исключением того, что находится в `node_modules`. Мы также хотим *разрешать* (`resolve`) эти два расширения при `import` модулей (т.е. эти расширения можно будет опускать при импорте - прим. пер.) 82 | 83 | **Примечание**: Расширение `.babel.js` сообщает Webpack применять трансформации Babel к данному конфигурационному файлу. 84 | 85 | `babel-loader` - это плагин для Webpack, транспилирующий код, так же как мы это делали с начала этого руководства. Единственное на данный момент отличие, что этот код исполняется в браузере а не на сервере. 86 | 87 | - Запустите `yarn add --dev webpack webpack-dev-server babel-core babel-loader` 88 | 89 | `babel-core` is a peer-dependency of `babel-loader`, so we installed it as well. 90 | Мы установили также `babel-core`, поскольку это peer-dependency (требуемая зависимость) для `babel-loader`. 91 | 92 | - Добавьте `/dist/` в `.gitignore` 93 | 94 | ### Обновление задач 95 | 96 | В режиме разработки мы будем использовать `webpack-dev-server` чтобы пользоваться преимуществами Hot Module Reloading (позже в этой главе), а в продакшене мы просто используем `webpack`, чтобы сгенерировать сборку. В обоих случаях, флаг `--progress` будет полезен для вывода дополнительной информации когда Webpack компилирует файлы. В продакшене мы также передаем в `webpack` флаг `-p` для минификации кода и переменную `NODE_ENV` установленную в `production`. 97 | 98 | Давайте обновим наши `scripts` чтобы реализовать это, а также улучшим некоторые другие задачи: 99 | 100 | ```json 101 | "scripts": { 102 | "start": "yarn dev:start", 103 | "dev:start": "nodemon -e js,jsx --ignore lib --ignore dist --exec babel-node src/server", 104 | "dev:wds": "webpack-dev-server --progress", 105 | "prod:build": "rimraf lib dist && babel src -d lib --ignore .test.js && cross-env NODE_ENV=production webpack -p --progress", 106 | "prod:start": "cross-env NODE_ENV=production pm2 start lib/server && pm2 logs", 107 | "prod:stop": "pm2 delete server", 108 | "lint": "eslint src webpack.config.babel.js --ext .js,.jsx", 109 | "test": "yarn lint && flow && jest --coverage", 110 | "precommit": "yarn test", 111 | "prepush": "yarn test && yarn prod:build" 112 | }, 113 | ``` 114 | 115 | В `dev:start` мы явно указываем расширения для наблюдения: `.js` и `.jsx`, и добавляем `dist` в игнорируемые директории. 116 | 117 | Мы создали отдельную задачу `lint` и добавили `webpack.config.babel.js` в список проверяемых файлов. 118 | 119 | - Затем давайте создадим контейнер для нашего приложения в `src/server/render-app.js` и включим его в генерируемую сборку: 120 | 121 | ```js 122 | // @flow 123 | 124 | import { APP_CONTAINER_CLASS, STATIC_PATH, WDS_PORT } from '../shared/config' 125 | import { isProd } from '../shared/util' 126 | 127 | const renderApp = (title: string) => 128 | ` 129 | 130 | 131 | ${title} 132 | 133 | 134 | 135 |
136 | 137 | 138 | 139 | ` 140 | 141 | export default renderApp 142 | ``` 143 | 144 | В зависимости от того, какое у нас окружение, мы включаем сборку Webpack Dev Server либо продакшен сборку. Обратите внимание на *виртуальный* путь к сборке Webpack Dev Server: `dist/js/bundle.js`, который на самом деле не читается с жесткого диска в режиме разработки. Также необходимо задать для Webpack Dev Server порт отличный от основного веб порта. 145 | 146 | - И наконец, в `src/server/index.js`, настройте сообщение от `console.log` таким образом: 147 | 148 | ```js 149 | console.log(`Server running on port ${WEB_PORT} ${isProd ? '(production)' : 150 | '(development).\nKeep "yarn dev:wds" running in an other terminal'}.`) 151 | ``` 152 | 153 | Это даст другим разработчикам подсказку, что делать, если они просто пытаются запустить `yarn start` без Webpack Dev Server. 154 | 155 | Хорошо, мы произвели много изменений, давайте посмотрим, все ли работает как ожидалось: 156 | 157 | 🏁 Запустите `yarn start` в терминале. Откройте другую вкладку или окошко с терминалом и запустите в ней `yarn dev:wds`. Как только Webpack Dev Server завершит генерацию сборки и sourcemap карт (вместе должно быть ~600kB файлов), и оба процесса повиснут в терминале, откройте `http://localhost:8000/` и вы должны увидеть "Hello Webpack!". Откройте консоль Chrome и на вкладке Source проверьте какие файлы включены. Вы должны увидеть только `static/css/style.css` под `localhost:8000/`, а все ваши исходные ES6 файлы должны располагаться в `webpack://./src`. Это значит, что sourcemap работают. Попробуйте изменить `Hello Webpack!` в `src/client/index.js` на любую другую строку с помощью редактора. Как только вы сохраните файл, вы должны увидеть в терминале, что Webpack Dev Server сгенерировал новую сборку, и вкладка Chrome автоматически обновилась. 158 | 159 | - Завершите предыдущие процессы в терминалах с помощью Ctrl+C, затем запустите `yarn prod:build` и затем `yarn prod:start`. Откройте `http://localhost:8000/`, и вы по прежнему должны видеть "Hello Webpack!". На этот раз, во вкладке Source консоли Chrome под `localhost:8000/` должно быть `static/js/bundle.js`, но без исходников в `webpack://`. Кликните на `bundle.js` чтобы убедиться, что он минифицирован. Запустите `yarn prod:stop`. 160 | 161 | Отличная работа, знаю, это было довольно плотно. Вы заслужили перерыв! Следующий раздел будет легче. 162 | 163 | **Примечание**: Я бы рекомендовал открывать как минимум 3 терминала: один для сервера Express, один для Webpack Dev Server и один для Git, тестов и основных команд, таких как установка пакетов с помощью `yarn`. В идеале, нужно разделить окно терминала на несколько панелей, чтобы видеть их все. 164 | 165 | ## React 166 | 167 | > 💡 **[React](https://facebook.github.io/react/)** - библиотека для построения пользовательских интерфейсов от Facebook. Она использует синтаксис **[JSX](https://facebook.github.io/react/docs/jsx-in-depth.html)** для представления HTML элементов и компонентов, сочетая его с мощью JavaScript. 168 | 169 | В этой части мы будем генерировать некоторый текст с помощью React и JSX. 170 | 171 | Для начала, давайте установим React и ReactDOM: 172 | 173 | - Запустите `yarn add react react-dom` 174 | 175 | Переименуйте файл `src/client/index.js` в `src/client/index.jsx` и напишите в нем следующий React код: 176 | 177 | ```js 178 | // @flow 179 | 180 | import 'babel-polyfill' 181 | 182 | import React from 'react' 183 | import ReactDOM from 'react-dom' 184 | 185 | import App from './app' 186 | import { APP_CONTAINER_SELECTOR } from '../shared/config' 187 | 188 | ReactDOM.render(, document.querySelector(APP_CONTAINER_SELECTOR)) 189 | ``` 190 | 191 | - Создайте фпйл `src/client/app.jsx` содержащий: 192 | 193 | ```js 194 | // @flow 195 | 196 | import React from 'react' 197 | 198 | const App = () =>

Hello React!

199 | 200 | export default App 201 | ``` 202 | 203 | Поскольку мы тут используем синтаксис JSX, нам нужно чтобы Babel трансформировал его с помощью пресета `babel-preset-react`. Заодно, мы добавим плагин для Babel `flow-react-proptypes`, который автоматически генерирует PropTypes из аннотаций Flow для React компонентов. 204 | 205 | 206 | - Запустите `yarn add --dev babel-preset-react babel-plugin-flow-react-proptypes` и отредактируйте файл `.babelrc` так: 207 | 208 | ```json 209 | { 210 | "presets": [ 211 | "env", 212 | "flow", 213 | "react" 214 | ], 215 | "plugins": [ 216 | "flow-react-proptypes" 217 | ] 218 | } 219 | ``` 220 | 221 | 🏁 Запустите `yarn start` и `yarn dev:wds`, откройте `http://localhost:8000`. Вы должны увидеть "Hello React!". 222 | 223 | Теперь попробуйте изменить текст в `src/client/app.jsx` на какой-нибудь другой. Webpack Dev Server должен автоматически перезагрузить страницу, что довольно изящно, но мы собираемся сделать даже еще лучше. 224 | 225 | ## Hot Module Replacement 226 | 227 | > 💡 **[Hot Module Replacement](https://webpack.js.org/concepts/hot-module-replacement/)** (*HMR*) - мощная способность Webpack заменять модули на лету без перезагрузки целой страницы. 228 | 229 | Чтобы заставить HMR работать с React, нам потребуется немного поднастроить. 230 | 231 | - Запустите `yarn add react-hot-loader@next` 232 | 233 | - Отредактируйте `webpack.config.babel.js` так: 234 | 235 | ```js 236 | import webpack from 'webpack' 237 | // [...] 238 | entry: [ 239 | 'react-hot-loader/patch', 240 | './src/client', 241 | ], 242 | // [...] 243 | devServer: { 244 | port: WDS_PORT, 245 | hot: true, 246 | }, 247 | plugins: [ 248 | new webpack.optimize.OccurrenceOrderPlugin(), 249 | new webpack.HotModuleReplacementPlugin(), 250 | new webpack.NamedModulesPlugin(), 251 | new webpack.NoEmitOnErrorsPlugin(), 252 | ], 253 | ``` 254 | 255 | - Отредактируйте файл `src/client/index.jsx`: 256 | 257 | ```js 258 | // @flow 259 | 260 | import 'babel-polyfill' 261 | 262 | import React from 'react' 263 | import ReactDOM from 'react-dom' 264 | import { AppContainer } from 'react-hot-loader' 265 | 266 | import App from './app' 267 | import { APP_CONTAINER_SELECTOR } from '../shared/config' 268 | 269 | const rootEl = document.querySelector(APP_CONTAINER_SELECTOR) 270 | 271 | const wrapApp = AppComponent => 272 | 273 | 274 | 275 | 276 | ReactDOM.render(wrapApp(App), rootEl) 277 | 278 | if (module.hot) { 279 | // flow-disable-next-line 280 | module.hot.accept('./app', () => { 281 | // eslint-disable-next-line global-require 282 | const NextApp = require('./app').default 283 | ReactDOM.render(wrapApp(NextApp), rootEl) 284 | }) 285 | } 286 | ``` 287 | 288 | Нам нужно, чтобы `App` был дочерним по отношению к `AppContainer` из `react-hot-loader`, и также нам требуется добавить `require` для получения следующей версии `App` при hot-reloading (горячей перезагрузке). Чтобы сделать этот процесс ясным и следовать принципу DRY, мы создали небольшую функию `wrapApp`, которую используем в обоих местах, где требуется генерировать `App`. Вы можете перенести `eslint-disable global-require` в начало файла чтобы сделать его более читабельным. 289 | 290 | 🏁 Перезапустите процесс `yarn dev:wds`, если онивсе еще запущен. Откройте `localhost:8000`. В консоли, вы должы увидеть некоторые логи об HMR. Возьмите и измените что-нибудь в `src/client/app.jsx`, и ваши изменения будут отражены в браузере через несколько секунд без полной перезагрузки страницы. 291 | 292 | Следующий раздел: [05 - Redux, Immutable, Fetch](05-redux-immutable-fetch.md#readme) 293 | 294 | Назад в [предыдущий раздел](03-express-nodemon-pm2_ru.md#readme) или [содержание](https://github.com/verekia/js-stack-from-scratch#table-of-contents). -------------------------------------------------------------------------------- /tutorial/08-bootstrap-jss.md: -------------------------------------------------------------------------------- 1 | # 08 - Bootstrap and JSS 2 | 3 | Code for this chapter available in the [`master-no-services`](https://github.com/verekia/js-stack-boilerplate/tree/master-no-services) branch of the [JS-Stack-Boilerplate repository](https://github.com/verekia/js-stack-boilerplate). 4 | 5 | Alright! It's time to give our ugly app a facelift. We are going to use Twitter Bootstrap to give it some base styles. We'll then add a CSS-in-JS library to add some custom styles. 6 | 7 | ## Twitter Bootstrap 8 | 9 | > 💡 **[Twitter Bootstrap](http://getbootstrap.com/)** is a library of UI components. 10 | 11 | There are 2 options to integrate Bootstrap in a React app. Both have their pros and cons: 12 | 13 | - Using the official release, **which uses jQuery and Tether** for the behavior of its components. 14 | - Using a third-party library that re-implements all of Bootstrap's components in React, like [React-Bootstrap](https://react-bootstrap.github.io/) or [Reactstrap](https://reactstrap.github.io/). 15 | 16 | Third-party libraries provide very convenient React components that dramatically reduce the code bloat compared to the official HTML components, and integrate greatly with your React codebase. That being said, I must say that I am quite reluctant to use them, because they will always be *behind* the official releases (sometimes potentially far behind). They also won't work with Bootstrap themes that implement their own JS. That's a pretty tough drawback considering that one major strength of Bootstrap is its huge community of designers who make beautiful themes. 17 | 18 | For this reason, I'm going to make the tradeoff of integrating the official release, alongside with jQuery and Tether. One of the concerns of this approach is the file size of our bundle of course. For your information, the bundle weights about 200KB (Gzipped) with jQuery, Tether, and Bootstrap's JS included. I think that's reasonable, but if that's too much for you, you should probably consider an other option for Bootstrap, or even not using Bootstrap at all. 19 | 20 | ### Bootstrap's CSS 21 | 22 | - Delete `public/css/style.css` 23 | 24 | - Run `yarn add bootstrap@4.0.0-alpha.6` 25 | 26 | - Copy `bootstrap.min.css` and `bootstrap.min.css.map` from `node_modules/bootstrap/dist` to your `public/css` folder. 27 | 28 | - Edit `src/server/render-app.jsx` like so: 29 | 30 | ```html 31 | 32 | ``` 33 | 34 | ### Bootstrap's JS with jQuery and Tether 35 | 36 | Now that we have Bootstrap's styles loaded on our page, we need the JavaScript behavior for the components. 37 | 38 | - Run `yarn add jquery tether` 39 | 40 | - Edit `src/client/index.jsx` like so: 41 | 42 | ```js 43 | import $ from 'jquery' 44 | import Tether from 'tether' 45 | 46 | // [right after all your imports] 47 | 48 | window.jQuery = $ 49 | window.Tether = Tether 50 | require('bootstrap') 51 | ``` 52 | 53 | That will load Bootstrap's JavaScript code. 54 | 55 | ### Bootstrap Components 56 | 57 | Alright, it's time for you to copy-paste a whole bunch of files. 58 | 59 | - Edit `src/shared/component/page/hello-async.jsx` like so: 60 | 61 | ```js 62 | // @flow 63 | 64 | import React from 'react' 65 | import Helmet from 'react-helmet' 66 | 67 | import MessageAsync from '../../container/message-async' 68 | import HelloAsyncButton from '../../container/hello-async-button' 69 | 70 | const title = 'Async Hello Page' 71 | 72 | const HelloAsyncPage = () => 73 |
74 | 81 |
82 |
83 |

{title}

84 | 85 | 86 |
87 |
88 |
89 | 90 | export default HelloAsyncPage 91 | ``` 92 | 93 | - Edit `src/shared/component/page/hello.jsx` like so: 94 | 95 | ```js 96 | // @flow 97 | 98 | import React from 'react' 99 | import Helmet from 'react-helmet' 100 | 101 | import Message from '../../container/message' 102 | import HelloButton from '../../container/hello-button' 103 | 104 | const title = 'Hello Page' 105 | 106 | const HelloPage = () => 107 |
108 | 115 |
116 |
117 |

{title}

118 | 119 | 120 |
121 |
122 |
123 | 124 | export default HelloPage 125 | ``` 126 | 127 | - Edit `src/shared/component/page/home.jsx` like so: 128 | 129 | ```js 130 | // @flow 131 | 132 | import React from 'react' 133 | import Helmet from 'react-helmet' 134 | 135 | import ModalExample from '../modal-example' 136 | import { APP_NAME } from '../../config' 137 | 138 | const HomePage = () => 139 |
140 | 146 |
147 |
148 |

{APP_NAME}

149 |
150 |
151 |
152 |
153 |
154 |

Bootstrap

155 |

156 | 157 |

158 |
159 |
160 |

JSS (soon)

161 |
162 |
163 |

Websockets

164 |

Open your browser console.

165 |
166 |
167 |
168 | 169 |
170 | 171 | export default HomePage 172 | ``` 173 | 174 | - Edit `src/shared/component/page/not-found.jsx` like so: 175 | 176 | ```js 177 | // @flow 178 | 179 | import React from 'react' 180 | import Helmet from 'react-helmet' 181 | import { Link } from 'react-router-dom' 182 | import { HOME_PAGE_ROUTE } from '../../routes' 183 | 184 | const title = 'Page Not Found!' 185 | 186 | const NotFoundPage = () => 187 |
188 | 189 |
190 |
191 |

{title}

192 |
Go to the homepage.
193 |
194 |
195 |
196 | 197 | export default NotFoundPage 198 | ``` 199 | 200 | - Edit `src/shared/component/button.jsx` like so: 201 | 202 | ```js 203 | // [...] 204 | 210 | // [...] 211 | ``` 212 | 213 | - Create a `src/shared/component/footer.jsx` file containing: 214 | 215 | ```js 216 | // @flow 217 | 218 | import React from 'react' 219 | import { APP_NAME } from '../config' 220 | 221 | const Footer = () => 222 |
223 |
224 |
225 |

© {APP_NAME} 2017

226 |
227 |
228 | 229 | export default Footer 230 | ``` 231 | 232 | - Create a `src/shared/component/modal-example.jsx` containing: 233 | 234 | ```js 235 | // @flow 236 | 237 | import React from 'react' 238 | 239 | const ModalExample = () => 240 |
241 |
242 |
243 |
244 |
Modal title
245 | 246 |
247 |
248 | This is a Bootstrap modal. It uses jQuery. 249 |
250 |
251 | 252 |
253 |
254 |
255 |
256 | 257 | export default ModalExample 258 | ``` 259 | 260 | - Edit `src/shared/app.jsx` like so: 261 | 262 | ```js 263 | const App = () => 264 |
265 | ``` 266 | 267 | This is an example of a *React inline style*. 268 | 269 | This will translate into: `
` in your DOM. We need this style to push the content under the navigation bar, but that's what's important here. [React inline styles](https://speakerdeck.com/vjeux/react-css-in-js) are a great way to isolate your component's styles from the global CSS namespace, but it comes at a price: You cannot use some native CSS features like `:hover`, Media Queries, animations, or `font-face`. That's [one of the reasons](https://github.com/cssinjs/jss/blob/master/docs/benefits.md#compared-to-inline-styles) we're going to integrate a CSS-in-JS library, JSS, later in this chapter. 270 | 271 | - Edit `src/shared/component/nav.jsx` like so: 272 | 273 | ```js 274 | // @flow 275 | 276 | import $ from 'jquery' 277 | import React from 'react' 278 | import { Link, NavLink } from 'react-router-dom' 279 | import { APP_NAME } from '../config' 280 | import { 281 | HOME_PAGE_ROUTE, 282 | HELLO_PAGE_ROUTE, 283 | HELLO_ASYNC_PAGE_ROUTE, 284 | NOT_FOUND_DEMO_PAGE_ROUTE, 285 | } from '../routes' 286 | 287 | const handleNavLinkClick = () => { 288 | $('body').scrollTop(0) 289 | $('.js-navbar-collapse').collapse('hide') 290 | } 291 | 292 | const Nav = () => 293 | 313 | 314 | export default Nav 315 | ``` 316 | 317 | There is something new here, `handleNavLinkClick`. One issue I encountered using Bootstrap's `navbar` in an SPA is that clicking on a link on mobile does not collapse the menu, and does not scroll back to the top of the page. This is a great opportunity to show you an example of how you would integrate some jQuery / Bootstrap-specific code in your app: 318 | 319 | ```js 320 | import $ from 'jquery' 321 | // [...] 322 | 323 | const handleNavLinkClick = () => { 324 | $('body').scrollTop(0) 325 | $('.js-navbar-collapse').collapse('hide') 326 | } 327 | 328 | 329 | ``` 330 | 331 | **Note**: I've removed accessibility-related attributes (like `aria` attributes) to make the code more readable *in the context of this tutorial*. **You should absolutely put them back**. Refer to Bootstrap's documentation and code samples to see how to use them. 332 | 333 | 🏁 Your app should now be entirely styled with Bootstrap. 334 | 335 | ## The current state of CSS 336 | 337 | In 2016, the typical modern JavaScript stack settled. The different libraries and tools this tutorial made you set up are pretty much the *cutting-edge industry standard* (*cough – even though it could become completely outdated in a year from now – cough*). Yes, that's a complex stack to set up, but at least, most front-end devs agree that React-Redux-Webpack is the way to go. Now regarding CSS, I have some pretty bad news. Nothing settled, there is no standard way to go, no standard stack. 338 | 339 | SASS, BEM, SMACSS, SUIT, Bass CSS, React Inline Styles, LESS, Styled Components, CSSX, JSS, Radium, Web Components, CSS Modules, OOCSS, Tachyons, Stylus, Atomic CSS, PostCSS, Aphrodite, React Native for Web, and many more that I forget are all different approaches or tools to get the job done. They all do it well, which is the problem, there is no clear winner, it's a big mess. 340 | 341 | The cool React kids tend to favor React inline styles, CSS-in-JS, or CSS Modules approaches though, since they integrate really well with React and solve programmatically many [issues](https://speakerdeck.com/vjeux/react-css-in-js) that regular CSS approaches struggle with. 342 | 343 | CSS Modules work well, but they don't leverage the power of JavaScript and its many features over CSS. They just provide encapsulation, which is fine, but React inline styles and CSS-in-JS take styling to an other level in my opinion. My personal suggestion would be to use React inline styles for common styles (that's also what you have to use for React Native), and use a CSS-in-JS library for things like `:hover` and media queries. 344 | 345 | There are [tons of CSS-in-JS libraries](https://github.com/MicheleBertoli/css-in-js). JSS is a full-featured, well-rounded, and [performant](https://github.com/cssinjs/jss/blob/master/docs/performance.md) one. 346 | 347 | ## JSS 348 | 349 | > 💡 **[JSS](http://cssinjs.org/)** is a CSS-in-JS library to write your styles in JavaScript and inject them into your app. 350 | 351 | Now that we have some base template with Bootstrap, let's write some custom CSS. I mentioned earlier that React inline styles could not handle `:hover` and media queries, so we'll show a simple example of this on the homepage using JSS. JSS can be used via `react-jss`, a library that is convenient to use with React components. 352 | 353 | - Run `yarn add react-jss` 354 | 355 | Add the following to your `.flowconfig` file, as there is currently a Flow [issue](https://github.com/cssinjs/jss/issues/411) with JSS: 356 | 357 | ```flowconfig 358 | [ignore] 359 | .*/node_modules/jss/.* 360 | ``` 361 | 362 | ### Server-side 363 | 364 | JSS can render styles on the server for the initial rendering. 365 | 366 | - Add the following constants to `src/shared/config.js`: 367 | 368 | ```js 369 | export const JSS_SSR_CLASS = 'jss-ssr' 370 | export const JSS_SSR_SELECTOR = `.${JSS_SSR_CLASS}` 371 | ``` 372 | 373 | - Edit `src/server/render-app.jsx` like so: 374 | 375 | ```js 376 | import { SheetsRegistry, SheetsRegistryProvider } from 'react-jss' 377 | // [...] 378 | import { APP_CONTAINER_CLASS, JSS_SSR_CLASS, STATIC_PATH, WDS_PORT } from '../shared/config' 379 | // [...] 380 | const renderApp = (location: string, plainPartialState: ?Object, routerContext: ?Object = {}) => { 381 | const store = initStore(plainPartialState) 382 | const sheets = new SheetsRegistry() 383 | const appHtml = ReactDOMServer.renderToString( 384 | 385 | 386 | 387 | 388 | 389 | 390 | ) 391 | // [...] 392 | 393 | 394 | // [...] 395 | ``` 396 | 397 | ## Client-side 398 | 399 | The first thing the client should do after rendering the app client-side, is to get rid of the server-generated JSS styles. 400 | 401 | - Add the following to `src/client/index.jsx` after the `ReactDOM.render` calls (before `setUpSocket(store)` for instance): 402 | 403 | ```js 404 | import { APP_CONTAINER_SELECTOR, JSS_SSR_SELECTOR } from '../shared/config' 405 | // [...] 406 | 407 | const jssServerSide = document.querySelector(JSS_SSR_SELECTOR) 408 | // flow-disable-next-line 409 | jssServerSide.parentNode.removeChild(jssServerSide) 410 | 411 | setUpSocket(store) 412 | ``` 413 | 414 | Edit `src/shared/component/page/home.jsx` like so: 415 | 416 | ```js 417 | import injectSheet from 'react-jss' 418 | // [...] 419 | const styles = { 420 | hoverMe: { 421 | '&:hover': { 422 | color: 'red', 423 | }, 424 | }, 425 | '@media (max-width: 800px)': { 426 | resizeMe: { 427 | color: 'red', 428 | }, 429 | }, 430 | specialButton: { 431 | composes: ['btn', 'btn-primary'], 432 | backgroundColor: 'limegreen', 433 | }, 434 | } 435 | 436 | const HomePage = ({ classes }: { classes: Object }) => 437 | // [...] 438 |
439 |

JSS

440 |

Hover me.

441 |

Resize the window.

442 | 443 |
444 | // [...] 445 | 446 | export default injectSheet(styles)(HomePage) 447 | ``` 448 | 449 | Unlike React inline styles, JSS uses classes. You pass styles to `injectSheet` and the CSS classes end up in the props of your component. 450 | 451 | 🏁 Run `yarn start` and `yarn dev:wds`. Open the homepage. Show the source of the page (not in the inspector) to see that the JSS styles are present in the DOM at the initial render in the `