Руководство по созданию React шаблона с нуля

Для создания наших React проектов, мы часто используем готовые шаблоны (boilerplate). Создаются они с помощью команды create-react-app. Сегодня мы с вами будем создавать собственный шаблон с нуля.

Шаблон, который мы с вами создадим, доступен здесь!

Начало работы

Прежде всего, мы создадим папку для запуска нашего шаблона. Назовем её react-bolt.

Откройте терминал и введите следующее:

mkdir react-bolt

Теперь, перейдите в созданную папку и введите команду:

npm init -y

NPM создаст package.json файл, там будут все установленные зависимости.

Структура папок для нашего шаблон должна выглядеть так:

react-bolt
    |--config
    |--src
    |--tests

Webpack

Сегодня Webpack — самый известный модульный сборщик для приложений JavaScript. По сути, он объединяет весь ваш код и генерирует один или несколько бандлов. Почитать о webpack можно здесь.

В этом шаблоне мы будем использовать webpack, поэтому установим необходимые зависимости:

npm install --save-dev webpack webpack-cli webpack-dev-server webpack-merge html-webpack-plugin clean-webpack-plugin img-loader url-loader file-loader

Теперь в нашей папке config создаём папкуwebpack, а затем в папке webpack создадим 5 файлов.

Создаём файл paths.js. Внутри этого файла будет целевой каталог для всех выходных файлов.

Внутри него вставляем этот код:

import path from 'path';

module.exports = {
    root: path.resolve(__dirname, '../', '../'),
    outputPath: path.resolve(__dirname, '../', '../', 'build'),
    entryPath: path.resolve(__dirname, '../', '../', 'src/index.js'),
    templatePath: path.resolve(__dirname, '../', '../', 'src/index.html'),
    imagesFolder: 'images',
    fontsFolder: 'fonts',
    cssFolder: 'css',
    jsFolder: 'js'
};

Теперь создаём файл rules.js, и вставляем туда следующий код:

module.exports = [
    {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
            loader: 'babel-loader'
        }
    },
    {
        test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
        exclude: /node_modules/,
        loader: 'file-loader'
    },
    {
        test: /\.(woff|woff2)$/,
        exclude: /node_modules/,
        loader: 'url-loader?prefix=font/&limit=5000'
    },
    {
        test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
        exclude: /node_modules/,
        loader: 'url-loader?limit=10000&mimetype=application/octet-stream'
    },
    {
        test: /\.(jpe?g|png|gif|svg)$/i,
        use: ['url-loader?limit=10000', 'img-loader']
    }
];

После этого мы создаём еще 3 файла:

webpack.common.babel.js

import webpack from 'webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';

import paths from './paths';
import rules from './rules';

module.exports = {
    entry: paths.entryPath,
    module: {
        rules
    },
    resolve: {
        modules: ['src', 'node_modules'],
        extensions: ['*', '.js', '.scss', '.css']
    },
    plugins: [
        new webpack.ProgressPlugin(),
        new HtmlWebpackPlugin({
            template: paths.templatePath,
            minify: {
                collapseInlineTagWhitespace: true,
                collapseWhitespace: true,
                preserveLineBreaks: true,
                minifyURLs: true,
                removeComments: true,
                removeAttributeQuotes: true
            }
        })
    ]
};

webpack.dev.babel.js

import webpack from 'webpack';

import paths from './paths';
import rules from './rules';

module.exports = {
    mode: 'development',
    output: {
        filename: '[name].js',
        path: paths.outputPath,
        chunkFilename: '[name].js'
    },
    module: {
        rules
    },
    performance: {
        hints: 'warning',
        maxAssetSize: 450000,
        maxEntrypointSize: 8500000,
        assetFilter: assetFilename => {
            return (
                assetFilename.endsWith('.css') || assetFilename.endsWith('.js')
            );
        }
    },
    optimization: {
        splitChunks: {
            chunks: 'all'
        }
    },
    devServer: {
        contentBase: paths.outputPath,
        compress: true,
        hot: true,
        historyApiFallback: true
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin()
    ]
};

webpack.prod.babel.js

import CleanWebpackPlugin from 'clean-webpack-plugin';

import paths from './paths';
import rules from './rules';

module.exports = {
    mode: 'production',
    output: {
        filename: `${paths.jsFolder}/[name].[hash].js`,
        path: paths.outputPath,
        chunkFilename: '[name].[chunkhash].js'
    },
    module: {
        rules
    },
    plugins: [
        new CleanWebpackPlugin([paths.outputPath.split('/').pop()], {
            root: paths.root
        })
    ],
    devtool: 'source-map'
};

В нашем webpack.common.babel.js файле, мы настроили конфигурацию входа и выхода и включили необходимые плагины. В webpack.dev.babel.js установили режим разработки. И в webpack.prod.babel.js установили режим производства.

После этого в корневой папке создаём последний файл webpack с именем webpack.config.js и вводим следующее:

require('@babel/register');
const webpackMerge = require('webpack-merge');

const common = require('./config/webpack/webpack.common.babel');

const envs = {
    development: 'dev',
    production: 'prod'
};

/* eslint-disable global-require,import/no-dynamic-require */
const env = envs[process.env.NODE_ENV || 'development'];
const envConfig = require(`./config/webpack/webpack.${env}.babel`);
module.exports = webpackMerge(common, envConfig);

Наша конфигурация Webpack готова, продолжим работать над другими частями шаблона (boilerplate).

Babel

Все, кто работает с React, наверняка слышали о Babel. Babel — это транспилер, который преобразует ваш код JavaScript в обычный ES5 JavaScript, который может работать в любом браузере.

Мы будем использовать несколько плагинов Babel, поэтому в нашей корневой папке установите:

npm install --save-dev @babel/core @babel/cli @babel/node @redux-logger @babel/plugin-proposal-class-properties @babel/plugin-proposal-object-rest-spread @babel/plugin-syntax-dynamic-import @babel/plugin-syntax-import-meta @babel/plugin-transform-async-to-generator @babel/plugin-transform-runtime @babel/preset-env @babel/preset-react @babel/register @babel/runtime babel-eslint babel-jest babel-loader babel-core@7.0.0-bridge.0

После этого мы создадим файл в корневой папке под названием .babelrc и внутри этого файла мы поместим следующий код:

{
    "presets": [
        [
            "@babel/preset-env",
            {
                "useBuiltIns": "entry"
            }
        ],
        "@babel/preset-react"
    ],
    "plugins": [
        "@babel/plugin-proposal-object-rest-spread",
        "@babel/plugin-transform-runtime",
        "@babel/plugin-transform-async-to-generator",
        "@babel/plugin-proposal-class-properties"
    ]
}

Теперь с Babel мы можем использовать синтаксис современного JavaScript без каких-либо проблем.

ESLint

Самый используемый инструмент для анализа качества кода — это ESLint. Он помогает находить некоторые ошибки, связанные с областью видимости переменных, присвоением необъявленных переменных и так далее.

Сначала установим следующие зависимости:

npm install --save-dev eslint eslint-config-airbnb eslint-config-prettier eslint-loader eslint-plugin-babel eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-prettier eslint-plugin-react 

Затем в корневой папке создаём файл .eslintrc и помещаем туда следующий код:

{
    "parser": "babel-eslint",
    "extends": ["airbnb", "prettier", "prettier/react"],
    "plugins": ["prettier"],
    "parserOptions": {
        "ecmaVersion": 6,
        "ecmaFeatures": {
            "jsx": true
        }
    },
    "env": {
        "browser": true,
        "node": true,
        "mocha": true,
        "es6": true,
        "jest": true
    },
    "rules": {
        "indent": ["error", 4],
        "space-before-function-paren": "off",
        "react/prefer-stateless-function": "warn",
        "react/jsx-one-expression-per-line": "off",
        "import/no-extraneous-dependencies": [
            "error",
            { "devDependencies": true }
        ],
        "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
        "linebreak-style": "off",
        "global-require": "off",
        "semi": "warn",
        "arrow-body-style": "off",
        "no-multiple-empty-lines": ["warn", { "max": 1 }],
        "no-unused-expressions": [
            "error",
            {
                "allowTaggedTemplates": true
            }
        ],
        "no-underscore-dangle": [
            2,
            { "allow": ["__REDUX_DEVTOOLS_EXTENSION__"] }
        ]
    }
}

Prettier

Prettier — это средство форматирования кода. Он анализирует код и переписывает его по установленным правилами. Подробнее можно почитать здесь

Установим его:

npm install --save-dev prettier

В корневой папке создадим файл .prettierrc и вставим следующий код:

{
    "printWidth": 80,
    "tabWidth": 4,
    "semi": true,
    "singleQuote": true,
    "bracketSpacing": true
}

React

React — это JavaScript-библиотека с открытым исходным кодом для разработки пользовательских интерфейсов. Мы полагаем, что вы уже знаете о React, но если хотите узнать больше, можете прочитать здесь.

Установим зависимости:

npm install --save react react-dom cross-env

Внутри папки src создадим простой HTML-файл index.html и добавим следующий код:

<!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 Bolt</title>
    </head>

    <body>
        <div id="root"></div>
    </body>
</html>

После этого мы создаём простой React проект. В папке src создаём index.js файл:

import React from 'react';
import ReactDOM from 'react-dom';

import App from './components/App';

ReactDOM.render(
    <App />,
    document.getElementById('root')
);

Внутри нашей папки src будет такая структура:

src
    |--actions
    |--components
    |--reducers
    |--reducers
    |--store

Создаём файл App.js в папке components и помещаем следующий код:

import React from 'react';

const App = () => <h1>React Bolt</h1>;

export default App;

Redux

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

Сначала установим зависимости:

npm install --save redux react-redux redux-thunk

Затем мы создаём Redux хранилище. В папке хранилища создаём index.js файл и вставляем следующий код:

import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import logger from 'redux-logger';

import rootReducer from '../reducers';

const middleware = applyMiddleware(thunk, logger);

const reduxDevTools =
    window.__REDUX_DEVTOOLS_EXTENSION__ &&
    window.__REDUX_DEVTOOLS_EXTENSION__();

const store = createStore(
    rootReducer,
    compose(
        middleware,
        reduxDevTools
    )
);

export default store;

Теперь внутри папки reducers создаём index.js и вставляем следующий код:

import { combineReducers } from 'redux';

const rootReducer = combineReducers({
    state: () => ({})
});

export default rootReducer;

Наконец, переходим к нашему index.js в папке src, оборачиваем код с помощью <Provider /> и передаём наш store в качестве параметра(props), чтобы сделать его доступным для нашего приложения.

Выглядит это так:

import React, { Fragment } from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';

import store from './store';
import App from './components/App';

ReactDOM.render(  
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById('root')
);

Наше Redux хранилище настроено и готово к работе.

React Router

React Router — это стандартная библиотека маршрутизации для React. Он синхронизирует пользовательский интерфейс (UI) с URL-адресом. Установим его:

npm install --save react-router-dom

После этого, открываем index.js в папке src и оборачиваем весь код с помощью <browser Router>. Должно выглядеть так:

import React, { Fragment } from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import { Provider } from 'react-redux';

import store from './store';
import App from './components/App';

ReactDOM.render(
    <BrowserRouter>       
        <Provider store={store}>
            <App />
        </Provider>
    </BrowserRouter>,
    document.getElementById('root')
);

Styled Components

Styled Components заставляют вас писать более мелкие компоненты и потом их переиспользовать. Если хотите узнать больше об этом, читайте здесь. Установим:

npm install --save styled-components

Затем, в нашем App.js в папке components мы создадим простой заголовок (title) используя Styled Components. Заголовок будет таким:


const Title = styled.h1`
    color: black;
    font-size: 2.5rem;
    font-weight: 700;
`;

В наш файл нужно импортировать Styled Components, он будет выглядеть следующим образом:

import React from 'react';
import styled from 'styled-components';

const Title = styled.h1`
    color: black;
    font-size: 2.5rem;
    font-weight: 700;
`;

const App = () => <Title>React Bolt</Title>;

export default App;

Jest & React Testing Library

Jest — это библиотека для тестирования JavaScript созданная Facebookом. React Testing Library — это очень простое решение для тестирования компонентов React. В принципе, эта библиотека является заменой Enzyme.

Каждое приложение нуждается в тестах. Настроим необходимые инструменты, чтобы начать тестирование.

Во-первых установим:

npm install --save-dev jest jest-dom react-testing-library

После этого переходим к нашему package.json и поместим в конце:

 "jest": {
    "setupFiles": [
        "<rootDir>/config/tests/jest.config"
    ],
    "transform": {
        "^.+\\.js$": "babel-jest"
    }
 }

Затем переходим в нашу папку config, внутри нее создаём папку tests и в этой папке создаём 2 файла.

Сначала создаём файл jest.config.js и добавляем следующий код:

module.exports = {
    automock: false,
    browser: false,
    bail: false,
    collectCoverageFrom: [
        'src/**/*.{js,jsx}',
        '!**/node_modules/**',
        '!**/vendor/**'
    ],
    coverageDirectory: '<rootDir>/coverage',
    globals: {
        __DEV__: true
    },
    moduleFileExtensions: ['js', 'json', 'jsx', 'node'],
    transform: {
        '^.+\\.js?$': 'babel-jest'
    },
    verbose: true,
    setupTestFrameworkScriptFile: './rtl.setup.js'
};

Затем создаём файл rtl.setup.jsи добавляем следующий код:


// See https://github.com/kentcdodds/react-testing-library#global-config
import 'jest-dom/extend-expect';
import 'react-testing-library/cleanup-after-each';

Наш шаблон(boilerplate) готов и мы можем его использовать.

Перейдём к нашему package.json и добавим следующий код:


"scripts": {
    "start": "cross-env NODE_ENV=development webpack-dev-server --open",
    "build": "cross-env NODE_ENV=production webpack",
    "lint": "eslint ./src/**/**.js",
    "lint:fix": "eslint ./src/**/**.js --fix",
    "test": "jest",
    "test:watch": "npm run test --watch",
    "test:cover": "npm run test --coverage"
}

Теперь, если мы запустим команду npm start и перейдём на localhost: 8080, то увидим, что наше приложение прекрасно работает.

Оригинал статьи

7 комментариев

Добавить комментарий для Дмитрий Отменить ответ

  • «Мы знаем всё о веб разработке» => Снизу гифка на 450 кб на новом сайте в 2019.
    А ещё, Христа ради, сделайте уточку «UP» справа, возле скролла, до неё ведь не удобно тянуться, ведь это элементарное требование UX, которое «сплывает» после прочитки первой статьи на вашем сайте.
    А вообще, по UX и доступности у вас беда, пока не особо у вас получилось в веб.