Для создания наших 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
, то увидим, что наше приложение прекрасно работает.
Опечатка @babe/cli
Дмитрий, спасибо за Ваши замечания. Поправили
Добавьте установку ‘redux-logger’
«Мы знаем всё о веб разработке» => Снизу гифка на 450 кб на новом сайте в 2019.
А ещё, Христа ради, сделайте уточку «UP» справа, возле скролла, до неё ведь не удобно тянуться, ведь это элементарное требование UX, которое «сплывает» после прочитки первой статьи на вашем сайте.
А вообще, по UX и доступности у вас беда, пока не особо у вас получилось в веб.
Везде где видел, скрол вверх — слва
Опечатка
redux-logger, а не @redux-logger
src
|—actions
|—components
|—reducers
|—reducers
|—store
Здесь повторение reducers