Разбираемся в тех технологиях, о которых ходят слухи, что они сложные
Сегодня мы разберёмся с тем, как устроен create-react-app под собой.
Окей, мы поставили преттир и настроили Еслинт на работу с дефолтным конфигом CRA (eslint-config-react-app
).
Еслинт это утилита, которая анализирует ваш код по заданным правилам.
Работает он сам по себе легко: вы ставите eslint
как зависимость и в файле конфига задаёте настройки, по которым он работает.
Настроек много, но нас интересуют три: правила (rules
), плагины (plugins
) и расширение через готовый конфиг (extends
).
Основа Еслинта это правила: вы указываете готовое правило (список из дефолтных) и как Еслинту нужно себя вести.
Пример: вы пишите код и хотите знать когда у вас остался мусор — вы объявили какую-нибудь константу или функцию, но не используете её. За это отвечает правило no-unused-vars (уточню, что vars
тут в контексте слова variables
, а не var
из Джса).
Вы создаёте .eslintrc.json
и там прописываете это правило:
{
"rules": {
"no-unused-vars": "error"
}
}
"error"
означает что это грубая ошибка. Бывает ещё "off"
и "warn"
.
В ES6 (он же ES2015) — версия стандарта ECMAScript, по которому работает Джс — добавили let
и const
.
Как вы помните из второго урока, отличие в том, что const
это неизменяемое значение, а у let
область видимости ограничена блоком, где она объявлена (но при этом переопределять можно).
Мы не хотим использовать var
, поэтому нам поможет правило no-var
.
В .eslintrc.json
дописываем
{
"rules": {
"no-unused-vars": "error",
+ "no-var": "error"
}
}
Но как запретить использовать и let
тоже? Для этого есть плагин eslint-plugin-fp
, он форсит функциональное программирование, в том числе и иммутабельность.
Плагины подключаются легко: ставите как зависимость, потом в .eslintrc.json
прописываете поле plugins
с массивом используемых плагинов, а в rules
— правило, где префиксом будет название плагина. Щас покажу!
{
+ "plugins": ["fp"],
"rules": {
"no-unused-vars": "error",
"no-var": "error",
+ "fp/no-let": "error"
}
}
(кстати, в перечислении плагинов можно опустить префикс eslint-plugin-
и оставить только fp
)
Есть очень много плагинов, например, eslint-plugin-react
который отвечает за работу с Реактом или eslint-plugin-import
который смотрит за импортами.
Но Женя, неужели это всё руками прописывать надо? Огромные же конфиги получаются, плюс Еслинт и плагины обновляются, правила добавляются, мне что, за этим следить постоянно?
Слава богу нет! У многих плагинов есть рекомендуемая конфигурация, как и у Еслинта. Использование поля extends
описано в документации.
Экстендить можно только несколько конфигов, например, можно взять eslint:recommended
, plugin:fp/recommended
и plugin:react/recommended
:
{
"plugins": ["fp", "react"],
+ "extends": [
+ "eslint:recommended",
+ "plugin:fp/recommended",
+ "plugin:react/recommended"
+ ],
"rules": {
"no-unused-vars": "error",
"no-var": "error",
"fp/no-let": "error"
}
}
При этом правила, которые объявлены в rules
, будут перебивать те, что указаны в самих конфигах.
Эйрбнб очень много вкладываются в опенсорс и много лет назад написали популярный стайлгайд по Джаваскрипту — советую его прочитать.
Что такое стайлгайд? Это правила. С чем работает еслинт? С правилами. В итоге ребята сделали свой конфиг по этому стайлгайду: eslint-config-airbnb
.
Он самый жёсткий из всех: ваш код будет на 99% красным, но зато он будет самым красивым и правильным из всех, что когда-либо показывали на откликах и собеседованиях ❤️
Когда вы поставите конфиг от Эйрбнб, Еслинт может сломаться и начать выдавать unexpected token =
и другие ошибки. За это отвечает парсер — Еслинт не может спарсить (проанализировать) ваш код.
По-умолчанию Еслинт использует Espree, но он не обновлялся долгое время и поэтому не поддерживает ES6 и другие нововведения Джаваскрипта (помните про babel-plugin-transform-class-properties
?). Но, к счастью, парсер в Еслинте можно сменить через поле parser
, вам понадобится babel-eslint
.
Как вы понимаете, использовать легко: ставите как зависимость, указываете в поле parser
{
+ "parser": "eslint",
"plugins": ["fp", "react"],
"extends": [
"eslint:recommended",
"plugin:fp/recommended",
"plugin:react/recommended"
],
"rules": {
"no-unused-vars": "error",
"no-var": "error",
"fp/no-let": "error"
}
}
Кстати, о Бейбеле мы ещё не говорили.
Бейбель это компилятор Джаваскрипта в Джаваскрипт.
Если вы проходили курс по вёрстке, то там есть урок про ПостЦСС и cssnext — способ писать будущий ЦСС уже сегодня. Достигается это за счёт того, что новый код превращается в старый с помощью ПостЦСС.
Бэйбель работает так же: вы пишите современный код (но который не поддерживают браузеры), прогоняете его Бейбелем и получаете тот, что поддерживается браузером.
Хотите узнать как работают компиляторы? Почитайте The Super Tine Compiler.
Помните, в третьем уроке мы говорили про JSX и что это всего лишь синтаксический сахар и на самом деле Реакт построен на методах React.createElement()
? Именно этим и занимается Бейбель и плагин babel-plugin-transform-react-jsx
: превращает удобный для вас JSX в понятный браузеру React.createElement()
. Попробуйте в REPL.
// input
import React from "react";
const HelloWorld = () => <div>Hello, world</div>;
<HelloWorld />;
// output
("use strict");
var _react = require("react");
var _react2 = _interopRequireDefault(_react);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
var HelloWorld = function HelloWorld() {
return _react2.default.createElement("div", null, "Hello, world");
};
_react2.default.createElement(HelloWorld, null);
У Бейбеля есть много плагинов, но, как и Еслинт, есть готовые пакеты — они называются пресетами (preset
).
Раньше были популярны stage-*
-пресеты, но потом от них решили отказаться в пользу babel-preset-env
.
Что за стейджи? Стандарт Джаваскрипта (ECMAScript) развивается, им занимается комитет TC39. Работает он так: кто-то описывает новую функциональность в Джаваскрипте, кидает пропозал (proposal), комитет обсуждает на одной из встречи и двигает по стейджам: от 0 (самый сырой черновик) до 4 (вошло в стандарт). Список всех текущих пропозалов можно увидеть в репозитории tc39/proposals.
Например, в стандарте уже есть Array.prototype.includes
, async функции, методы Object.entries()
и Object.values()
и другие.
Раз они есть в стандарте — браузеры должны реализовать, но мы же не можем так долго ждать, нам нужно писать код с этими фичами уже сегодня! Для этого и существуют плагины Бейбеля, например, babel-plugin-array-includes
.
На самом деле, этот блок про Бейбель чисто теоретический: в CRA он настолько хорошо настроен, что вам вряд ли придётся с ним столкнуться.
CRA использует собственный пресет babel-preset-react-app
, куда уже включены плагины babel-plugin-transform-object-rest-spread
и babel-plugin-transform-react-jsx
, но если бы вы хотели написать свой конфиг, то ваш .babelrc
выглядел бы так:
{
"plugins": ["transform-object-rest-spread", "transform-react-jsx"]
}
(как и у Еслинта, префикс babel-plugin-
можно опустить)
Как видите, ничего сложного. Но что делать потом? Вам нужно скомпилировать ваш исходник в джс, который понятен браузеру.
Поставьте babel-cli
и запустите его как yarn run babel [src] -d [dest]
, где src
это директория с исходниками, а dest
это куда нужно скопировать результат. Пример: yarn run babel src -d build
.
Всем этим (и другим) занимается Вебпак.
Вебпак — это бандлер модулей. В какой-то момент создатель Вебпака подумал: а зачем нам копировать файлы, подключать всё в index.html
по-старинке, если мы можем в джс-файлах импортить вообще всё что угодно?
Про Вебпак ходит очень много слухов, что он сложный, но это не так: он сложный когда вам нужно заниматься тонкими оптимизациями.
Давайте попробуем собрать демку в отдельном проекте без CRA? Напишем тот же Hello World на Реакте и соберём всё Вебпаком.
Конечная структура у нас будет такая:
webpack-demo
├── .gitignore # пропишем сами
├── package.json # создастся через yarn init
├── webpack.config.js
├── node_modules
│ └── ...
├── src # создадим руками
│ └── index.js # наше Реакт-приложение
├── build # создаётся Вебпаком
│ └── bundle.js
└── public # создадим руками
└── index.html
Файлы src/index.js
и public/index.html
мы создадим руками, а потом — вызовем yarn init
чтобы создать package.json
.
И не забудьте сделать Гитигнор, иначе туда улетит build
и node_modules
, а этого не стоит делать — в репозитории должны быть только исходники.
# dependencies
/node_modules
# production
/build
# misc
.DS_Store
.vscode
yarn-error.log
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>React App</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
import React from "react";
import { render } from "react-dom";
function App() {
return <h1>Hello, world</h1>;
}
render(<App />, document.getElementById("app"));
После этого ставим через Ярн webpack-cli
и webpack-dev-server
: yarn add --dev webpack-cli webpack-dev-server
.
Первый будет билдить наш проект, второй — отслеживать изменения когда мы работаем локально.
// подключим модуль `path` из ноды,
// чтобы построить абсолютный путь
// до директории build
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.join(__dirname, "build")
}
};
Теперь пробуем запустить как yarn webpack
и увидим ошибку:
WARNING in configuration
The 'mode' option has not been set. Set 'mode' option to 'development' or 'production' to enable defaults for this environment.
ERROR in ./src/index.js
Module parse failed: Unexpected token (6:4)
You may need an appropriate loader to handle this file type.
| function App() {
| return (
| <h1>Hello, world</h1>
| )
| }
Ну, во-первых, поставим ещё mode: "development"
в webpack.config.js
, а, во-вторых, исправим ошибку.
Про настройку Вебпака ходит много смешков, потому что сам он ничего не умеет. Он как Бейбель: расширяется через плагины, только тут они называются лоадерами.
Например, если мы импортим ЦСС-файл, то нам нужны лоадеры style-loader
и css-loader
и Вебпак вставит ЦСС-код в <style></style>
теги в начале страницы.
Если мы импортим шрифты, картинки или ещё что, то нужен будет file-loader
, который при импорте вернёт сгенерированный путь к файлу.
import logo from "./logo.svg";
console.log(logo); // "/static/images/0dcbbaa7013869e351f.png"
Но как нам работать с ошибкой выше? Мы же написали нормальный код, джаваскриптовский!
Нам понадобится babel-loader
c пресетами babel-preset-env
(для современного Джаваскрипта с const
, async-await
и прочим) и babel-preset-react
(для Джсх).
yarn add --dev babel-loader babel-core babel-preset-env babel-preset-react
И, конечно же, нужно отредактировать конфиг Вебпака, чтобы он знал, что на .js
файлы нужно натравить именно babel-loader
.
const path = require("path");
module.exports = {
mode: "development",
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.join(__dirname, "build")
},
module: {
rules: [
{
test: /\.js$/, // регулярное выражение
exclude: path.join(__dirname, "node_modules"),
use: {
loader: "babel-loader",
options: {
presets: ["babel-preset-env", "babel-preset-react"]
}
}
}
]
}
};
Когда я писал этот урок, Бейбел был ещё 6.x версии. В 7.x пакеты вместо
babel-x
стали@babel/x
. Для этого, кстати, используется Scoped Packages в Нпм/Ярн.
Пробуем запустить?
webpack-demo $ yarn webpack
yarn run v1.5.1
$ /private/tmp/webpack-demo/node_modules/.bin/webpack
Hash: 708efebfede93aa32071
Version: webpack 4.1.1
Time: 700ms
Built at: 2018-3-20 10:48:12
Asset Size Chunks Chunk Names
bundle.js 3.54 KiB main [emitted] main
Entrypoint main = bundle.js
[./src/index.js] 430 bytes {main} [built]
ERROR in ./src/index.js
Module not found: Error: Can't resolve 'react' in '/private/tmp/webpack-demo/src'
@ ./src/index.js 3:13-29
ERROR in ./src/index.js
Module not found: Error: Can't resolve 'react-dom' in '/private/tmp/webpack-demo/src'
@ ./src/index.js 7:16-36
Ага, опять ошибки, но теперь понятные: нужно просто поставить react
и react-dom
. Установили, пробуем ещё раз:
webpack-demo $ yarn webpack
yarn run v1.5.1
$ /private/tmp/webpack-demo/node_modules/.bin/webpack
Hash: c1cba3ec70e6f308e028
Version: webpack 4.1.1
Time: 1341ms
Built at: 2018-3-20 10:49:36
Asset Size Chunks Chunk Names
bundle.js 634 KiB main [emitted] [big] main
Entrypoint main [big] = bundle.js
[./src/index.js] 430 bytes {main} [built]
+ 23 hidden modules
✨ Done in 2.35s.
Готово! Проверяем нашу директорию build
и видим красивый файл bundle.js
Его же мы и подключаем в public/index.html
и потом открываем в браузере.
А что делать, если вы очень много разрабатываете и постоянно вызывать yarn webpack
утомляет?
Для этого есть webpack-dev-server
: небольшой сервер, который крутится на http://localhost:8080/
и раздаёт файлы оттуда.
webpack-demo $ yarn webpack-dev-server
yarn run v1.5.1
$ /private/tmp/webpack-demo/node_modules/.bin/webpack-dev-server
ℹ 「wds」: Project is running at http://localhost:8080/
ℹ 「wds」: webpack output is served from /
ℹ 「wdm」: Hash: 58502b2391c6e006927e
Version: webpack 4.1.1
Time: 1931ms
Нас интересуют две строчки:
ℹ 「wds」: Project is running at http://localhost:8080/
ℹ 「wds」: webpack output is served from /
Первая означает, что Вебпак раздаёт файлы по адресу http://localhost:8080/
, а вторая — что файлы находятся в корне, этого адреса.
Поэтому нам нужно заменить в public/index.html
адрес до Джс-файла:
<script src="../build/bundle.js"></script>
<script src="http://localhost:8080/bundle.js"></script>
Для вас команда фейсбука и CRA сделали одну небольшую команду react-script build
(она же прописана в package.json > scripts > build
чтобы её можно было вызвать как yarn build
). Об этом лучше прочитать в официальной документации CRA.
Качество кода многое говорит о разработчике. Можно руками соблюдать какой-нибудь стайлгайд, а можно воспользоваться Еслинтом, настроить его и радоваться жизни.
Плюс мы узнали как работает Бейбель — парсер и компилятор Джаваскрипта в… Джаваскрипт; плюс разобрались как развивается ECMAScript.
И третьим пунктом мы прошлись по Вебпаку — мощному, но несложному для новичков бандлеру модулей.
В вашем проекте воткните самый жёсткий конфиг Еслинта: airbnb-config-eslint. Не забудьте проверить проект на ошибки, вызвав Еслинт.
Учтите, что установка этого конфига не самая тривиальная, поэтому читайте внимательно документацию: там недостаточно написать yarn add --dev eslint-config-airbnb
.
Демо с Вебпаком я выложил на Гитхаб, для вас есть задания:
- нужен работающий
import './styles.css'
, - нужен работающий
import logo from './logo.png'
, гдеlogo
будет ссылкой на файл, - нужно настроить Вебпак так, чтобы можно было делать
import Button from 'ui/Button
вместоimport Button from '../../../../../../../../../ui/Button
.
Для первых двух задания вам нужны лоадеры, для третьего — настройка конфига Вебпака.
Не забывайте задавать вопросы в чате — там обязательно помогут 💪🏻
Помните: нет глупых вопросов, есть лишь страх их задавать.