Создание проекта React & MobX не отличается от приготовления штруделя.

Я более уверен в пикантных рецептах, лазанья и паста на томатной основе - в моей зоне комфорта. Но на этот раз у меня было несколько запасных яблок, и я решил приготовить штрудель.

Если вы не знаете, что такое штрудель:

Прежде чем приступить к ингредиентам, здесь вы можете найти пошаговый рецепт maxgallo/strude-recipe!

Ингредиенты

Выпечка, яблоки, изюм, корица, цедра лимона, сахар и панировочные сухари.

Каждый рецепт начинается с ингредиентов. Неважно, дала ли вам бабушка секретную книгу кулинарии или вы следите за учебником на YouTube, их выбор - первый шаг.

Вот ингредиенты, которые я использовал для приготовления своего рецепта / проекта:

  • Реагировать 200гр
  • Webpack & Babel по вкусу
  • Модули CSS 1tsp
  • MobX 100гр
  • Горячая перезагрузка 50гр
  • AVA 1⁄2 стакана
  • Фермент 3 ст. Л.

1. Подготовка

Вам понадобится большая миска, сковорода и форма для запекания. Разогрейте духовку до 190 ° (170 °, если газовая).

Для настройки проекта я буду использовать yarn и git. Как и в любом рецепте, не стесняйтесь использовать свои любимые инструменты.

$ mkdir strudel-recipe
$ cd strudel-recipe
$ git init
$ yarn init

2. Смешайте React с Webpack и Babel в чаше

Очистите и нарежьте яблоки, затем смешайте их с корицей, сахаром, цедрой лимона и изюмом в миске.

Вы, вероятно, уже много раз настраивали проект React и у вас хватит смелости сделать это самостоятельно. (если нет, я бы порекомендовал create-response-app).

Отправной точкой всегда является файлsrc/index.js

import React from 'react';
import { render } from 'react-dom';
import App from './components/App';
render(
    <App />,
    document.getElementById('app')
);

К сожалению, многие браузеры еще не способны понимать операторы import и export, и, что более важно, маловероятно, что они когда-либо будут поддерживать синтаксис <JSX>.

На помощь приходят Webpack и Babel.

$ yarn add --dev webpack webpack-dev-server
$ yarn add --dev babel-core babel-loader babel-preset-react babel-preset-env

Зачем мне пять пакетов?

Вам нужно webpack, потому что вы хотите объединить все свои файлы JavaScript. Вам нужно webpack-dev-server, потому что вы ленивы и не хотите писать собственный сервер Express, который перезагружается каждый раз, когда вы изменяете файл во время разработки. Вам нужно babel-core, потому что вы хотите транспилировать свой код. Вам нужно babel-preset-env, потому что вы хотите писать классные вещи для ES6 / ES7, не задумываясь, поддерживает ли браузер X функцию Y. Вам нужно babel-loader, потому что Webpack должен каким-то образом взаимодействовать с Babel. Вам нужно babel-reset-react, потому что вы хотите преобразовать свой JSX во что-то, что понимают браузеры.

На данном этапе это .babelrc файл

{
    "presets": [
        "react",
        "env"
    ]
}

и webpack.config.js выглядит вот так

const path = require("path");
module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, "public")
        filename: "bundle.js"
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                use: {
                    loader: 'babel-loader',
                }
            }
        ]
    }
};

Здесь Babel начинает с ./src/index.js и разрешает все зависимости, создавая выходной файл вpublic/bundle.js. Каждый раз, когда он находит новый модуль, он будет использовать соответствующий загрузчик для обработки этого типа файла (подробнее здесь). В приведенной выше конфигурации мы сообщаем Webpack, что каждый раз, когда он находит .js модуль, он должен спрашивать babel-loader, как с ним обращаться.

Нам просто нужно определить скрипт для запуска нашего проекта в package.json

"start": "webpack-dev-server --content-base public"

3. Поджарьте некоторые CSS-модули.

Обжарьте панировочные сухари на чистой сковороде до золотистого цвета.

Благодаря модулям CSS мы можем иметь селекторы с ограниченной областью видимости (подробнее здесь). В React это означает, что мы можем импортировать файл CSS из JavaScript и использовать селекторы, определенные в файле CSS, непосредственно в компоненте React.

Это App.js компонент

import React, { Component } from 'react';
import style from './app.css';
class App extends Component {
    render() {
        return (
            <div className={style.appTitle}>
                Strudel Recipe
            </div>
        )
    }
}
export default App

А это файл app.css

.app-title {
    font-size: 20px;
    color: coral;
}

Для этого нам понадобятся два загрузчика Webpack, которые будут заботиться о модулях CSS: yarn add --dev css-loader style-loader. Это новый загрузчик, который нужно добавить в конфиг webpack

{
    test: /\.css$/,
    use: [
        'style-loader',
        {
            loader: 'css-loader',
            options: {
                modules: true,
                localIdentName: '[local]__[hash:base64:5]',
                camelCase: true,
            },
        },
    ]
}

4. Раскатайте AVA и поместите немного фермента сверху.

Раскатать слоеное тесто. Поместите начинку по центру. Если заполнение слишком велико, не используйте его полностью.

Чтобы протестировать наше приложение с минимальной реакцией, мы добавим несколько библиотек.

$ yarn add --dev ava enzyme jsdom react-test-renderer

AVA - это Futuristic Test Runner, Enzyme - это набор утилит для тестирования для React. JSDom и react-test-renderer необходимы Enzyme.

Так выглядит наш App.test.js файл

import test from 'ava';
import React from 'react';
import { shallow } from 'enzyme';
import App from './App';
test('Renders a div with className and text', t => {
    const wrapper = shallow(<App />);
    t.true(wrapper.contains(
        <div className="appTitle">Strudel Recipe</div>
    ));
});

Этот тест написан с использованием синтаксиса ES6, и AVA выполняет за нас транспиляцию, но нам нужно быть осторожными.

4.1 Тестовые файлы AVA и исходные файлы

Тестовые файлы в AVA - это все файлы *.test.js. Внутри тестового файла вы можете импортировать другие файлы (например, import App from './App'). Эти импортированные файлы называются исходными файлами.

AVA позаботится о том, чтобы преобразовать тестовые файлы, а вам предоставит возможность преобразовать исходные файлы. Существует несколько способов настройки Babel в обоих случаях , вы можете подробнее прочитать здесь.

То, как AVA использует исходные файлы, является очень деликатной темой в сообществе AVA, и существует множество предложений по улучшению опыта и производительности в будущем.

4.2 Анализ конфигурации AVA

Это наша конфигурация AVA внутри package.json

"ava": {
    "babel": "inherit",
    "require": [
        "babel-register",
        "./test/helpers/setup-test-env.js",
        "mock-css-modules"
    ]
},

Первая строка “babel”: “inherit” означает, что мы собираемся унаследовать конфигурацию Babel по умолчанию для транспиляции файлов AVA Test. В нашем случае эта конфигурация находится внутри .babelrc. Исходные файлы всегда передаются с конфигурацией babel по умолчанию.

babel-register - это переопределение функции узла require, позволяющее использовать babel на лету. Благодаря этому мы можем на лету переносить исходные файлы наших тестов AVA (не забудьте yarn add --dev babel-register).

./test/helpers/setup-test-env.js - это просто стандартный способ правильно инициализировать Enzyme с помощью JSDOM.

4.2 CSS-модули с AVA

При использовании модулей CSS с Webpack css-loader заботится о создании уникального хеш-имени для каждого селектора CSS. В тестах мы не используем Webpack (можете, если хотите), но и нас не особо беспокоит уникальность CSS-селекторов.

Чтобы решить эту проблему, я нашел небольшую библиотеку под названием mock-css-modules, которая отменяет поведение функции require для .css файлов. Каждый раз, когда вы запрашиваете модуль CSS, он возвращает вам тот же ключ, который вы запрашивали. На практическом примере мы можем протестировать что-то вроде этого

<div className={style.appTitle}>
    Strudel Recipe
</div>

В этом случае

t.true(wrapper.contains(
    <div className="appTitle">Strudel Recipe</div>
));

5. Сложите тесто с помощью MobX.

Загните верхнюю и нижнюю части теста над начинкой и смажьте яичным желтком. Концы одинаково хорошо запечатайте яично-молочной смесью. Переверните штрудель изящной стороной вверх.

Я собираюсь добавить в этот проект MobX и MobX State Tree, и для этого мне нужно импортировать три зависимости.

yarn add mobx mobx-react mobx-state-tree

Первый mobx - это чистый MobX, второй mobx-react - это набор утилит для использования MobX в мире React. Третий - это контейнер состояния на базе MobX, который помогает вам думать, как управлять состоянием с помощью MobX.

Я не буду останавливаться на коде магазина, обновленного index.js и Recipe.js компонента, потому что эта статья посвящена настройке проекта.

Я хочу отметить, что, используя MobX, я только что представил необходимость использовать Декораторы (например, @inject @observer) и Свойства класса (например, anyIngredients) в моем коде JavaScript

import React, { Component } from 'react';
import { computed } from 'mobx';
import { inject, observer } from 'mobx-react';
@inject('recipeStore') @observer
class Recipe extends Component {
    @computed get anyIngredients() {
        return this.props.recipeStore.ingredients.length
    }
    render() {
        return (
            <div>
            ...more things

Оба они еще не являются стандартом, но вы все равно можете использовать их с парочкой плагинов Babel.

yarn add --dev babel-plugin-transform-decorators-legacy babel-plugin-transform-class-properties

и мы должны обновить .babelrc таким образом

{
    "presets": [
        "react",
        "env"
    ],
    "plugins": [
        "transform-decorators-legacy",
        "transform-class-properties"
    ]
}

Мой тест все еще работает? Да, потому что конфигурация babel для исходных файлов зависит от моего .babelrc файла, который я только что обновил.

Я также добавил новый тестовый файл для компонента Recipe, чтобы показать вам, как вкратце протестировать компоненты, которые используют Provider для внедрения stores.

6. Выпекать штрудель с горячей перезагрузкой.

Выпекайте штрудель 25/30 минут

Здесь задействованы два игрока: webpack-dev-server и react-hot-loader: в первом мы включаем замену модуля (независимо от фреймворка), во втором мы применяем эту функцию для мира React. Давайте установим недостающую зависимость с yarn add --dev react-hot-loader@next (мы используем @next версию).

Теперь мы можем создать новый скрипт в package.json для горячей перезагрузки.

"start:hot": "webpack-dev-server --config webpack.config.hot.js --hot --content-base public"

Из предыдущего мы просто добавили --hot и загружаем другую конфигурацию Webpack, которая расширяет первую.

const webpack = require('webpack');
const webpackConfig = require('./webpack.config');
webpackConfig.plugins = [
    new webpack.NamedModulesPlugin()
];
webpackConfig.entry = [
    'react-hot-loader/patch',
    webpackConfig.entry
];
module.exports = webpackConfig;

Мы добавляем плагин NamedModulesPlugin, который помогает нам распознавать перезагруженные модули с помощью Hot Reloading, и мы добавляем запись перед текущей, поэтому первое, что Webpack начинает объединять, это всегда react-hot-loader.

Последний кусок головоломки - это обновлениеindex.js

В строке 25 вы можете увидеть, что когда module.hot включен, мы добавляем слушателя к методу module.hot.accept. Каждый раз, когда что-то меняется, этот слушатель запускается, вызывая повторный рендеринг части дерева.

7. Индивидуальный подход: три тряски

Я купила ванильное мороженое, чтобы съесть штрудель. Мне нравится, когда в десертах чувствуется острая холодность.

С Webpack 2 мы получили Three Shaking бесплатно из коробки, но всегда ли он включен? Ответ - нет, и главная проблема в том, что Babel прямо сейчас трансформирует все наши ES6 модули в CommonJS модулей.

Сборщики Javascript, такие как webpack и Rollup, могут выполнять встряхивание дерева только для модулей, имеющих статическую структуру. Если модуль статический, то сборщик может определить его структуру во время сборки, безопасно удаляя код, который никуда не импортируется.

CommonJS модули не имеют статической структуры. Из-за этого webpack не сможет вытряхнуть неиспользуемый код из окончательного пакета.

Поэтому нам просто нужно сказать Babel, чтобы он прекратил преобразовывать наши модули, это просто

{
    "presets": [
        ["env", {
            "modules": false
        }],
        "react"
    ],
    "plugins": [
        "transform-decorators-legacy",
        "transform-class-properties"
    ]
}

Единственная проблема в том, что мы просто сломали наши тесты

Это связано с тем, что мы больше не преобразуем ES6 модулей в Babel, но нам по-прежнему нужно это делать для наших тестов AVA. Чтобы решить эту проблему, вы можете предоставить различные конфигурации Babel в зависимости от вашего process.env.NODE_ENV.

Итак, наш test скрипт внутри package.json теперь выглядит следующим образом: (чтобы быть совместимым с Windows, вам нужно использовать что-то вроде cross-env)

"test": "NODE_ENV=test ava"

И наш новый .babelrc, благодаря этой опции, содержит две разные настройки, которые Babel выбирает на основе переменной NODE_ENV.

Теперь все наши тесты могли безопасно выполняться с ES6, переданным Babel, а наша start:hot задача могла получить преимущество Трех встряхиваний.

8. Наслаждайтесь! (Buon Appetito)

Я знаю, что это не похоже на то, что было на картинке в начале, но этого было достаточно для первой попытки, и я узнаю пару вещей в следующий раз!