دولوپر فرانتاند - علاقمند به جاوااسکریپ (تایپاسکریپت)، ریاکت، ریاکتنیتیو - فرانتاند دولوپر سابق دیجیکالا، دیجیکالامگ، شیپور و سیبچه
مثال کاملی از پیاده سازی یک برنامه وبی پایه بر React
-- مقدمه
افرادی که در حوزه وب فعالیت میکنن، به راحتی میتونن جستجو بزنن و خیلی از موارد مربوط به یک برنامه وبی پایه بر React
رو ببینن و نمونه پیاده سازیهای اون رو داشته باشن، موارد و مفهومهای مثل:
- پایش سمت سرور (Server Side Rendering)
- استفاده از React Router
- استفاده از CSS Modules
- استفاده از PostCSS
- تنظیمات Webpack
- نوشتن Express
- تنظیمات برای جداسازی محیط develop و محیط production
- ابزارهای قوی برای Deploy برنامههای بزرگ
- مقیاس پذیری یا همون Scale کردن برنامهها
- برنامههای تک صفحهای (Single Page Application)
- برنامههای پراگرسیو (Progressive Web Application)
- و...
موارد زیاد هستن، و خیلی گیج کننده که گاهی همین زیادی تکنیکها و تکنولوژیها باعث میشه، اینها رو خوب کنار هم نگذاریم و چیز خوبی از آب در نیاد. یا اینکه کنار هم میگذاریم ولی چنگی به دل خودمون نمیزنه، یه چیزهایی رو توی سایتهای شرکتهای بزرگی مثل Pinterest یا Instagram یا Facebook و غیره، میبینیم که برامون سواله که اینها چطور عمل میکنند و چطور یک برنامه بزرگ React رو با تمام استانداردها پیکربندی میکنن و حجم عظیمی از برنامه نویسها کنار هم بدون داشتن کوچکترین تداخلی کار میکنند.
توی این مقاله قصد دارم با یک نگاه ساده یک مثال یا بهتره بگم که یه پلتفورم خوب، کامل و قابل بزرگ شدن در مقیاسهای عظیم رو قدم به قدم توضیح بدم.
در انتهای این مقاله شما یک ریپوی کامل دارید که میتونید یک برنامه React
ی خوب و تمیز داشته باشید که شامل خیلی از تکنیکها و تکنولوژیهای روز و ترند هست. خروجی شما خیلی خیلی شبیه کارهای خوب توی این حوزه خواهد بود و سعی کردم که همه گلها رو یک جا جمع کنم. ولی این دلیلی بر این نیست که شما از اینی که هست نتونید بهترش کنید.
خز؛پن ! (خیلی زیاد؛پس نخون) : اگر فاقد حوصله هستین و دوست دارید نتیجه رو نگاه کنید ریپو رو ببینین و ازش استفاده کنید.
چیزی که من دلم میخواد اینه که اگر شما هم ایده یا نگرشی برای بهتر کردن این ریپو دارید، یه سر به Github بزنید، Fork کنید و کارتون رو Pull Request کنید. من و Andrew، کاملا استقبال میکنیم.
-- میخوایم چی بسازیم؟
اگر یه چرخ بزنید برای دیدن مثالهای پیاده سازی یک React App موارد زیادی پیدا میکنید که هر کدوم ویژگیهایی رو دارند، مثل SSR
یا استفاده از React Helmet
و خیلی چیزهای دیگه اما بیشتر اونها برای اهداف develop
نوشته و انتشار داده میشن، هیچ کدوم هر دو هدف develop
و deploy
صحیح رو دنبال نمیکنن و استانداردهای این دو محیط رو اصلا بیان نمیکنن.
من توی این مقاله میخوام هم محیط develop
خوبی رو براتون شرح بدم که بدون خون ریزی و با سرعت زیاد کارتون رو انجام بدید هم محیط production
رو با حداقل استانداردها داشته باشیم. باز هم تاکید میکنم این مقاله فوق العاده ساده هست و موارد زیادی رو من توش نیاوردم. که میتونید شما بیارید و در تکمیل این مقاله، مقاله بدید و کدها رو هم به ریپوی اصلی Pull Request
بدید و اینطوری باعث پیشرفتش بشید.
-- ابزارها و تکنولوژیهای مورد استفاده در این مقاله:
- React
- React router 4
- React Helmet
- CSS Modules
- PostCSS
- Webpack 3
- Babel
- Express
- PM2
-- نیازمندی اولیه
در ابتدا شما باید Node
نصب داشته باشید، ترجیحاً آخرین نسخه پایدار، که اگر ندارید از سایت نود دانلود و نصبش کنید.
-- خوب حالا چیکار کنیم؟
قدم بعدی اینه که برای برنامهای که قراره بنویسیم یک فولدر بسازیم. از همین اول اتمام حجت کنم. با فولدر بندی، هم کار خودت دسته بندی میشه هم منظمتر میشه هم تا درصدی برای مقیاس پذیری داری آمادهاش میکنی. هرکسی در فولدر بندی شیوهای داره من اینطوری هستم که توی این مقاله هست. شما میتونی برای خودت اونی که احساس میکنی بهتره رو پیاده سازی کنی.
خوب مثلا اسم فولدر اصلیمون رو میذاریم react-example
و بعد بسته به سیستم عاملتون با محیط کامندی وارد فولدر مربوطه بشید، برای ویندوزیها cmd
یا powershell
هست و برای مک و لینوکس هم terminal
، من فرض میکنم که وارد شدید، حالا دستور زیر رو تایپ کنید:
npm init
این دستور در هنگام اجرا شدن چندتا سوال ازتون میپرسه که پاسخش رو بدید، نگران نباشید این سوال و جوابها برای ساختن فایل package.json
هست که به راحتی میتونید بعدا اصلاحش کنید، پس اصلا نگران نباشید. یک سری سوالاتش رو هم که حال نکردید اصلا جواب ندید و کلید اینتر رو بزنید که سریعتر از این مرحله خلاص بشیم.
خوب داره جالب میشه، من خودم خیلی ذوق دارم، حالا دستور زیر رو بزنید:
npm install --save react@16.3.2 react-dom@16.3.2
در واقع شما با دستور بالا دو dependency
اصلی این مقاله رو نصب کردید، فولدری کنار فایل package.json
ساخته میشه به نام node_modules
که این dependency
ها و الباقی موارد رو اونجا نگه میداره که شما خیلی تحویلشون نگیرید، فعلا توی اون مراحل نیستیم که به این موارد اهمیت بدیم. اون عددها هم نسخههایی هست که من دارم باهاشون کار میکنم و تاکید میکنم شما هم از همین نسخه استفاده کنید، چون این موارد خیلی زود بروز میشن و اگر شما خیلی دیر این مقاله رو بخونید ممکنه نسخههای جدید مشکلاتی ایجاد که عملا این مقاله رو بدرد نخور میکنه، برای همین شما با نسخهای که من وارد میکنم نصب کنید که اجرای موفقی رو داشته باشید و بعدش اگر دلتون خواست به نسخههای جدیدتر بروزرسانی کنید، این کار رو خودم کردم، Andrew زمانی که این استک رو نوشت داستانش فرق داشت، خیلی چیزها رو نداشت و از نسخههای پایینتری استفاده میکرد. من وقتی که مقالهاش رو خوندم حس کردم یه سری چیزها میتونه بروزتر بشه و همین کار رو هم کردم و با ویرایشهای دیگه بهش PR
دادم و این مثال خوب رو پیشرفت دادم.
میریم مرحله بعدی، خوب دستور زیر رو بزنید:
npm install --save-dev babel-loader@7.0.0 babel-core@6.24.1 webpack@3.11.0
اینها هم dependency
های این پروژه هستند منتهی برای محیط ساختن یا همون develop
برای همینه که بعد دستور install
از فرمان save-dev
استفاده کردم، بعدا که برید فایل package.json
رو بخونید متوجه میشید چطوری دارن جدا میشن و در آینده میگم که اصلا این نوع سوا کردنشون یعنی چی.
خوب حالا نوبت این شده که Babel
رو نصب کنیم، سوال اینه که این چیه؟ ساده بگم که قراره شما کدهای خیلی خیلی آپدیتی بزنید و از ویژگیهای جدید جاوااسکریپت استفاده کنید ولی قرار نیست وقتی خروجی میگیرید و برنامه رو برای deploy
آماده میکنید مرورگرها هم مثل شما خفن باشن، ممکنه مردم از مرورگرهای زمان قلی قلی میرزا استفاده کنن پس باید هوای اونها رو هم داشته باشیم. Babel
به شما اجازه میده خفن باشید و خفن کد بزنید ولی در نهایت زمانی که Webpack
برای شما خروجی میسازه، خروجیای میده بیرون که هر مرورگر پیرپاتال یا جوونی بفهمه. پس حالا کد زیر رو بزنید:
npm install --save-dev babel-preset-es2015@6.24.1 babel-preset-react@6.24.1 babel-preset-env@1.5.1 babel-preset-stage-0@6.24.1
اینها dependency
های مورد نیاز بری تنظیم Babel
توی این مقاله هستش، بسیاری پلاگین و dependency
برای Babel
هست که بعدا خودتون با نیازها و مشکلاتی که براتون پیش میاد باهاشون آشنا میشید، اینجا هم برای حداقلی اینها رو گذاشتم. روش استفادهاش هم اینه که شما باید یه فایل بسازی به نام .babelrc
که متاسفانه بخاطر زبان فارسی نقطه در انتها نمایش داده شده. اصل اسم به صورت زیر هست:
.babelrc
توی این فایل باید تنظیمات Babel
رو انجام بدید:
{
"presets": [
"env",
"es2015",
"react",
"stage-0"
]
}
هر کدوم از اینها یک معنایی داره مثلا es2015
برای اینه که کدهایی که ES6
ی زده میشه مثل class
یا constructor
تبدیل میشه به ES5
و این باعث میشه اون مرورگرهای قدیمی هم بفهمنش، یا مثلا react
باعث میشه که کدهای JSX
تبدیل به توابع جاوااسکریپتی React
بشن مثل createElement
. من بیشتر از توضیح نمیدم، میتونید در مورد الباقی جستجو و تحقیق کنید که چه به درد میخورن یا چه تنظیماتی دارند و چه چیزهای دیگهای میشه گذاشت، اگر سوال داشتید در همین مقاله در کامنت سوالاتتون رو بپرسید.
-- تنظیمات Webpack
تا الآن همه فایلها و تنظیمات پایهای بودن و همه رو توی روت پروژه یا همون فولدرمون گذاشتم، و از اینجا به بعد میخوام که فایلها رو دسته بندی بریم جلو، طوری که هم منظم باشن هم بدونیم چی کجاست. این نوع فولدربندی باعث میشه در آینده برای تغییرات هم راحتتر باشید هم به راحتی برنامهتون رو به مقیاس بزرگتری ببرید.
همونطور که در بالا گفته بودم ما دوتا محیط داریم، یکی برای ساختن یا develop
و یکی هم برای محیط عملیاتی یا production
، پس برای این منظور یک پوشه به نام webpack
بسازید و داخلش یک فایلی بسازید به نام webpack.development.config.js
و محتوای اون رو اینطوری بنویسید:
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const distDir = path.join(__dirname, '../dist');
const srcDir = path.join(__dirname, '../src');
module.exports = [
{
name: 'client',
target: 'web',
entry: `${srcDir}/client.jsx`,
output: {
path: path.join(__dirname, 'dist'),
filename: 'client.js',
publicPath: '/dist/',
},
resolve: {
extensions: ['.js', '.jsx']
},
devtool: 'source-map',
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules[\\\/])/,
use: [
{
loader: 'babel-loader',
}
]
},
{
test: /\.pcss$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader',
options: {
modules: true,
importLoaders: 1,
localIdentName: '[local]',
sourceMap: true,
}
},
{
loader: 'postcss-loader',
options: {
config: {
path: `${__dirname}/../postcss/postcss.config.js`,
}
}
}
]
})
},
],
},
plugins: [
new ExtractTextPlugin({
filename: 'styles.css',
allChunks: true
})
]
},
{
name: 'server',
target: 'node',
entry: `${srcDir}/server.jsx`,
output: {
path: path.join(__dirname, 'dist'),
filename: 'server.js',
libraryTarget: 'commonjs2',
publicPath: '/dist/',
},
resolve: {
extensions: ['.js', '.jsx']
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules[\\\/])/,
use: [
{
loader: 'babel-loader',
}
]
},
{
test: /\.pcss$/,
use: [
{
loader: 'isomorphic-style-loader',
},
{
loader: 'css-loader',
options: {
modules: true,
importLoaders: 1,
localIdentName: '[local]',
sourceMap: false
}
},
{
loader: 'postcss-loader',
options: {
config: {
path: `${__dirname}/../postcss/postcss.config.js`,
}
}
}
]
}
],
},
}
];
در این فایل میبینید که قسمت client و server رو جدا کردم و برای هر قسمت تنظیماتی گذاشتم که بعضیهاش مشابه هست و بعضیهاش فرق داره که اینجا خیلی توضیح نمیدونم، چون همینطوری این مقاله طولانی هست و توضیحات هر بخش اون یک مقاله جدا میخواد، اگر مایل بودید در کامنت یا توئیت به من امر کنید حتما توضیح میدم یا در صورت نیاز مقالهای جدا مینویسم.
-- تنظیمات PostCSS
خوب، لازمه بگم که ماجرای این پیش پردازشگر CSS
یکم از الباقی دوستانش جداست، شما نه تنها باید خودش رو نصب کنید، بلکه باید پلاگینهاش رو هم نصب کنید و اینطوری هی اون رو قوی و قویتر میکنید، هرجا احساس کردید کاری رو انجام نمیده جستجو کنید، حتما پلاگین جالبی براش هست، من برای سادگی نصب خودش و حداقل پلاگینهاش رو در زیر قرار میگذارم:
npm install --save-dev autoprefixer@8.3.0 css-loader@0.28.4 css-mqpacker@6.0.2 isomorphic-style-loader@4.0.0 postcss@6.0.21 postcss-apply@0.10.0 postcss-cssnext@3.1.0 postcss-custom-properties@7.0.0 postcss-extend@1.0.5 postcss-loader@2.1.4 postcss-nested@3.0.0 postcss-nested-ancestors@2.0.0 postcss-partial-import@4.1.0 postcss-scss@1.0.5 style-loader@0.21.0 extract-text-webpack-plugin@3.0.2
داخل روت پروژه فولدری به نام postcss
بسازید و داخلش فایلی بسازید به نام postcss.config.js
و تنظیمات این CSS Preprocessor
محبوب رو در اون قرار بدید، شاید بگید چرا از SCSS
استفاده نکردم. اگر در مورد PostCSS
یکم مطالعه کنید متوجه میشید چه فوایدی نصبت به الباقی هم خانوادههاش داره، یکی از فواید خوبش داشتن پلاگینی به نام autoprefixer
هست که عملا نیاز شما رو به mixin
ها از بین میبره و با یک تنظیم ساده در کد زیر میتونید سطح پوشش پشتیبانی از نظر CSS
رو روی مرورگرها تغییر بدید:
module.exports = {
ident: 'postcss',
syntax: 'postcss-scss',
map: {
'inline': true,
},
plugins: {
'postcss-partial-import': {
'prefix': '_',
'extension': '.pcss',
'glob': false,
'path': ['./../src/styles']
},
'postcss-nested-ancestors': {},
'postcss-apply': {},
'postcss-custom-properties': {},
'postcss-nested': {},
'postcss-cssnext': {
'features': {
'nesting': false
},
'warnForDuplicates': false
},
'postcss-extend': {},
'css-mqpacker': {
'sort': true
},
'autoprefixer': {
'browsers': ['last 15 versions']
},
}
};
-- حالا خود برنامه
خیلی واضحه که برنامه ما ممکنه چندین صفحه داشته باشه و باید چیزی وجود داشته باشه که مسیر این صفحات رو هندل کنه و اینجاست که من دوست عزیزم react-router
رو وارد بازی میکنم. فقط توجه داشته باشید من میخوام از نسخه آخر یا همون ۴ استفاده کنم برای همین با دستور زیر آخرین نسخهء ۴ رو نصب میکنم:
npm install --save react-router-dom@4.2.2
بعد از این کار یک فولدری بسازید به نام src
و در اون فایلی به نام client.jsx
بسازید و محتویات زیر رو در اون قرار بدید:
import React from 'react';
import {hydrate} from 'react-dom';
import {BrowserRouter} from 'react-router-dom';
import App from './app/App';
hydrate((
<BrowserRouter>
<App/>
</BrowserRouter>
), document.getElementById('root'));
بعد کنار همین فایلی که ساختید فایلی بسازید به نام server.jsx
و کدهای زیر رو در اون قرار بدید:
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import {StaticRouter} from 'react-router-dom';
import {Helmet} from "react-helmet";
import Template from './app/template';
import App from './app/App';
export default function serverRenderer({clientStats, serverStats}) {
return (req, res, next) => {
const context = {};
const markup = ReactDOMServer.renderToString(
<StaticRouter location={req.url} context={context}>
<App/>
</StaticRouter>
);
const helmet = Helmet.renderStatic();
res.status(200).send(Template({
markup: markup,
helmet: helmet,
}));
};
}
حالا کنار اینها دو پوشه بسازید، توی یکیش فایلهای اصلی React
یا همون برنامه خودمون رو مینویسیم و توی یکی دیگه قراره فایلهای PostCSS
یا همون استایلهای CSS
رو قرار بدیم، اولی رو app
و دومی رو styles
نامگذاری کنید.
-- -- فایلهای برنامه
داخل فولدر app
در ابتدا فایل به نام template.jsx
بسازید و تمپلیت اصلی html
ی که قراره همه چیز در اون تزریق بشه رو بسازید:
export default ({ markup, helmet }) => {
return `<!DOCTYPE html>
<html ${helmet.htmlAttributes.toString()}>
<head>
${helmet.title.toString()}
${helmet.meta.toString()}
${helmet.link.toString()}
</head>
<body ${helmet.bodyAttributes.toString()}>
<div id="root">${markup}</div>
<script src="/dist/client.js" async></script>
</body>
</html>`;
};
بعد یک فایلی بسازید به نام App.jsx
که این فایلی هست که برنامه اصلی رو داخلش مینویسید، من فعلا چیز سادهای مینویسم، اما شما به دلخواه خودتون میتونید پوشه بندی کنید و یک مدل بزرگتری رو خلق کنید:
import React, {Component} from 'react';
export default class App extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h1>Hello World!</h1>
</div>
);
}
}
یکم جلوتر دوباره سراغ این فایل میام و یکم بهش چیز میز اضافه میکنم اما فعلا بذاریم ساده باشه که یه چیز ساده بتونیم توی مرورگر تماشا کنیم که خیالمون راحت باشه همه چیز داره درست کار میکنه.
فقط یه چیز خیلی ساده اضافه کنم، react-helmet
، این یکی انقدر ساده هستش که نگو، و فوق العاده بدرد بخور، ازوناست که کلی فواید SEO
ی داره برامون. درواقع قسمت head
هر صفحه رو برامون dynamic
میسازه و کلی آپشن داره که میشه ازش به راحتی استفاده کرد، برای نصب این از دستور زیر استفاده کنید:
npm install --save react-helmet@5.2.0
فعلا فقط این رو نصب کنید، زمانی که دوباره به فایل App.jsx
برگشتیم، خودتون متوجه میشید که چه کارهای ساده ولی به درد بوخوری میشه باهاش کرد.
-- -- فایلهای استایل
داخل فولدر styles
یک فایل اصلی بسازید به نام styles.pcss
و کنارش یک فولدر بسازید به نام partials
و داخلش فایلی اضافه کنید به نام _partial.pcss
و محتویات زیر رو در اون کپی کنید:
// styles.pcss
@import "partials/partial";
.component {
@extend %box;
color: #2f95ff;
}
.text {
display: flex;
@extend %box;
}
.test {
display: flex;
}
.active {
color: red;
}
و
// partials/_partial.pcss
%box {
box-shadow: 0 0 10px 1px #ff6fc3;
}
-- تنظیمات سرور develop
سرور محیط develop
ما express.js
خواهد بود، در واقع به این صورت هست که ما یک سری middleware
استفاده میکنیم که زمانی که تغییراتی در فایلهای PostCSS
یا فایلهای برنامه ایجاد میکنیم، به عبارتی داریم develop
میکنیم، خودش خود به خود فایلهای نهایی رو بسازه و ما نتیجه آخرین تغییراتمون رو داخل مرورگر ببینیم.
ابتدا باید پکیجهای dependency رو نصب کنیم:
npm i --save-dev express@4.15.3 webpack-dev-middleware@2.0.6 webpack-hot-middleware@2.22.1 webpack-hot-server-middleware@0.5.0
بعد در روت پروژه یا همون فولدر react-example
پوشهای میسازیم به نام express
و تنظیمات محیط develop
رو برای express
درش قرار میدیم، برای این کار فایلی بسازید به نام development.js
و داخلش اینطوری بنویسید:
const express = require('express');
const app = express();
const webpack = require('webpack');
const config = require('./../webpack/webpack.development.config.js');
const compiler = webpack(config);
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');
const webpackHotServerMiddleware = require('webpack-hot-server-middleware');
app.use(webpackDevMiddleware(compiler, {
serverSideRender: true,
publicPath: "/dist/",
}));
app.use(webpackHotMiddleware(compiler.compilers.find(compiler => compiler.name === 'client')));
app.use(webpackHotServerMiddleware(compiler));
const PORT = process.env.PORT || 3000;
app.listen(PORT, error => {
if (error) {
return console.error(error);
} else {
console.log(`Development Express server running at http://localhost:${PORT}`);
}
});
-- ببینیم چی شد تا الآن
حالا وقتشه یه تست ریز بریم ببینم تا الآن چیکار کردیم، برای اینکه ببینیم همه چیز رو تا الآن درست به کار گرفتیم یا نه دستور زیر رو در محیط کامندیمون اجرا میکنیم:
node ./express/development.js
اگر ویندوز دارید و خطای NODE_ENV
رو دیدید اصلا نگران نشید به این آدرس یه سر بزنید، اونجا نوشتم که چیکار باید بکنید، اگر خطا نداد که باید توی محیط کامندیتون جمله زیر رو ببینید:
Development Express server running at http://localhost:3000
اگر آره که نصف راه رو به سلامت رسیدید و میتونید مرورگرتون رو با آدرس http://localhost:3000
باز کنید و از شاهکاری که کاشتید لذت ببرید.
-- تنظیمات React Router
تا الآن همه چیز فوق العاده بود. تقریبا کلی چیزای باحال رو کنار هم به درستی گذاشتیم و همه چیز با هم کار کرد. تاکید میکنم اگر مشکلی داشتید توی همین مقاله در قسمت کامنت مطرح کنید، دربست در خدمتم.
ولی، این react-router
رو فقط نصب کردیم و توی برنامه هنوز نیاوردیمش، اونجا که در مورد فایل App.jsx
گفتم که بر میگردم، آهان، الآن وقتشه که برگردیم با هم و اون رو به شکلی یکم کاملتر بنویسیم. فایل App.jsx
رو با کدهای زیر دوباره ویرایشش کنید:
import React, {Component} from 'react';
import Helmet from "react-helmet";
import {Switch, Route} from 'react-router-dom';
import {Link, NavLink} from 'react-router-dom';
import styles from '../styles/styles.pcss';
class Menu extends Component {
render() {
return (
<div>
<ul>
<li>
<NavLink exact to={'/'} activeClassName={styles.active}>Homepage</NavLink>
</li>
<li>
<NavLink activeClassName={styles.active} to={'/about'}>About</NavLink>
</li>
<li>
<NavLink activeClassName={styles.active} to={'/contact'}>Contact</NavLink>
</li>
</ul>
</div>
);
}
}
class Homepage extends Component {
render() {
return (
<div className={styles.component}>
<Helmet title="Welcome to our Homepage"/>
<Menu/>
<h1>Homepage</h1>
</div>
);
}
}
class About extends Component {
render() {
return (
<div>
<Helmet title="About us"/>
<Menu/>
<h1>About</h1>
</div>
);
}
}
class Contact extends Component {
render() {
return (
<div>
<Helmet title="Contact us"/>
<Menu/>
<h1>Contact</h1>
</div>
);
}
}
export default class App extends Component {
render() {
return (
<div>
<Helmet
htmlAttributes={{lang: "en", amp: undefined}} // amp takes no value
titleTemplate="%s | React App"
titleAttributes={{itemprop: "name", lang: "en"}}
meta={[
{name: "description", content: "Server side rendering example"},
{name: "viewport", content: "width=device-width, initial-scale=1"},
]}
link={[{rel: "stylesheet", href: "/dist/styles.css"}]}
/>
<Switch>
<Route exact path='/' component={Homepage}/>
<Route path='/about' component={About}/>
<Route path='/contact' component={Contact}/>
</Switch>
</div>
);
}
}
خوب یه توضیح ریز بدم، ببینید الآن توی این فایل App.jsx
جدیدمون سه تا کامپوننت جدید آوردیم به اسمهای Homepage
و About
و Contact
که در واقع نقش سه صفحه ما رو بازی میکنن.
بعدش یه کامپوننتی ساختیم به اسم Menu
که اولی همین فایل App.jsx
قرارش دادیم و اون رو هم توی هر سه کامپوننت ایمپورت کردیم تا در هر سه صفحه مجزایی که داریم منو هم دیده بشه.
نکتهای که وجود داره اینه که دیگه مثل گذشته قرار نیست که کلاسهای تگها رو اضافه کنیم. ما دیگه با کلاس شدیم و قراره که با CSS-Modules
کار کنیم. در طی روند این مقاله من خیلی ریز تنظیماتش رو وارد کردم و شما فقط نیازه بدونی که توی هر کامپوننتی که مجزاش میکنی باید با کد:
import styles from '../styles/styles.pcss';
استایلها رو مثل یک آبجکت اضافه کنید و توی هر تگ که خواستید کلاس بهش بدید از کد زیر استفاده کنید:
<div className={styles.container}>
<div className={styles['container-top']}
example
</div>
</div>
فکر کنم خیلی واضحه دیگه، توی فایلهای PostCSS
با هر اسمی که خواستید کلاسهاتون رو بسازید و اینجا به جای اینکه مستقیم اسمش رو صدا بزید اون رو به صورت فرزندی از آبجکت styles
صدا بزنید، این کار در نسخهای که برای deploy
آماده میکنید یا همون نسخه production
اسم کلاسها رو در DOM
به هش تبدیل میکنه و میتونید تا حداقل ۵
حرف ببریدش، اینطوری فایل CSS
آخری که میسازید فوق العاده فشرده شده خواهد بود. حالا بعدا، زمانی که دارم در مورد تنظیمات Webpack
در محیط production
صحبت میکنم بیشتر باهاش آشنا میشید.
نکتهء دیگهای که خیلی عاشقشم دوست خوبم Helmet
هست که به وضوح توی کدها دیده میشه، در هر کامپوننت به صورت مجزا وجود داره، در کامپوننت root
یا اصلی یا همون App
کامپوننت هم دیده میشه که تنظیمات کلیای رو داره اجرا میکنه. انقدر این تنظیمات ساده و خوانا هست که من اینجا از توضیحش صرف نظر میکنم.
حالا برای اینکه دوباره ببینم چیکاره هستیم یکبار دیگه دستور اجرا رو برای develop
اجرا میکنیم:
node ./express/development.js
به نظر معجزه میاد، آره، شما تونستید یه اسکلت عالی از یک برنامه پایه بر React
بسازید که همه چیزهای عالی رو داره. هر صفحه رو که با فشار منو صدا میزنید از سمت سرور داره پویش یا همون render
میشه و کاملا هم سمت مرورگر شما dynamic
هست. ولی به نظر شما کار ما تمام شده؟
هم آره هم نه، آره
: چون ما محیط develop
عالیای رو درست کردیم و نه
: چون هنوز نمیدونیم چطور باید اون رو برای deploy
روی سرور آماده کنیم.
-- آماده سازی برای اعزام به محیط عملیاتی
تا الآن هر کار کردیم، برای این بوده که محیط develop
خوبی رو داشته باشیم، ولی از الآن به بعد دیگه قراره که تنظیمات متفاوتی برای production
قرار بدیم که سه هدف اصلی رو حتما باید در نظر بگیریم:
- باندل کردن کل فایلهای برنامه و فشرده سازی و از بین بردن
debugger
ها وconsole.log
ها و تبدیل به نسخهES5.1
- استخراج فایل
styles.css
در یک فایل جدا و فشرده سازی و پاک کردن همه کامنتها - ساختن فایل
stats.json
که درواقع عواملیWebpack
ی که برای محیط عملیاتی در سرور برایexpress
نیاز هست رو توش نگه میداریم و اونهایی که برای محیطdevelop
بوده رو دیگه نمیخواهیم.
پیش به سوی تنظیمات برای محیط عملیاتی، کدهای زیر را اجرا میکنیم:
npm install --save-dev clean-webpack-plugin@0.1.19 stats-webpack-plugin@0.6.0 optimize-css-assets-webpack-plugin@3.2.0
خوب حالا باید توی فولدر webpack
یک فایل دیگه به نام webpack.production.config.js
بسازید و داخلش رو با کدهای زیر پر کنید:
const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const StatsPlugin = require('stats-webpack-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const distDir = path.join(__dirname, '../dist');
const srcDir = path.join(__dirname, '../src');
module.exports = [
{
name: 'client',
target: 'web',
entry: `${srcDir}/client.jsx`,
output: {
path: distDir,
filename: 'client.js',
publicPath: distDir,
},
resolve: {
extensions: ['.js', '.jsx']
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules[\\\/])/,
use: [
{
loader: 'babel-loader',
}
]
},
{
test: /\.pcss$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader',
options: {
modules: true,
importLoaders: 1,
localIdentName: '[hash:base64:10]',
sourceMap: false,
}
},
{
loader: 'postcss-loader',
options: {
config: {
path: `${__dirname}/../postcss/postcss.config.js`,
}
}
}
]
})
}
],
},
plugins: [
new ExtractTextPlugin({
filename: 'styles.css',
allChunks: true
}),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new CleanWebpackPlugin(distDir),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
screw_ie8: true,
drop_console: true,
drop_debugger: true
}
}),
new webpack.optimize.OccurrenceOrderPlugin(),
]
},
{
name: 'server',
target: 'node',
entry: `${srcDir}/server.jsx`,
output: {
path: distDir,
filename: 'server.js',
libraryTarget: 'commonjs2',
publicPath: distDir,
},
resolve: {
extensions: ['.js', '.jsx']
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules[\\\/])/,
use: [
{
loader: 'babel-loader',
}
]
},
{
test: /\.pcss$/,
use: [
{
loader: 'isomorphic-style-loader',
},
{
loader: 'css-loader',
options: {
modules: true,
importLoaders: 1,
localIdentName: '[hash:base64:10]',
sourceMap: false
}
},
{
loader: 'postcss-loader',
options: {
config: {
path: `${__dirname}/../postcss/postcss.config.js`,
}
}
}
]
}
],
},
plugins: [
new OptimizeCssAssetsPlugin({
cssProcessorOptions: {discardComments: {removeAll: true}}
}),
new StatsPlugin('stats.json', {
chunkModules: true,
modules: true,
chunks: true,
exclude: [/node_modules[\\\/]react/],
}),
]
}
];
و همینطور در فولدر express یک فایل به نام production.js برای محیط production بسازید و کدهای زیر رو داخلش کپی کنید:
const express = require('express');
const path = require('path');
const app = express();
const ClientStatsPath = path.join(__dirname, './../dist/stats.json');
const ServerRendererPath = path.join(__dirname, './../dist/server.js');
const ServerRenderer = require(ServerRendererPath).default;
const Stats = require(ClientStatsPath);
app.use('/dist', express.static(path.join(__dirname, '../dist')));
app.use(ServerRenderer(Stats));
const PORT = process.env.PORT || 3000;
app.listen(PORT, error => {
if (error) {
return console.error(error);
} else {
console.log(`Production Express server running at http://localhost:${PORT}`);
}
});
الآن دیگه باید بتونید با دستور زیر فایلهایی رو بسازید که قراره برای محیط عملیاتی به سرور اعزام بشه:
NODE_ENV=production webpack -p --config ./webpack/webpack.production.config.js --progress --profile --colors
مشاهده میکنید که فایلهایی در فولدر dist ساخته شده و اگر دستور زیر رو در محیط کامندیتون اجرا کنید، شما درواقع محیط عملیاتی رو شبیه سازی کردید. یجورایی چیز نهایی که مردم میبینند در واقع همینه:
NODE_ENV=production node ./express/production.js
اگر الآن روی مرورگر Google Chrome
دو افزونه React Developer Tools
و Wappalyzer
رو داشته باشید میبینید که علامت React
روش ظاهر شده و مخصوصا افزونه React Developer Tools
آبی شده و دیگه قرمز نیست. چون قرمز برای محیط دولوپ هست و آبی یعنی شما نسخه مخصوص production
رو بردی روی سرور.
جدا تبریک میگم. شما فوق العادهای، چون روند این مقاله در عین اینکه سادهترین شکل رو داشته ولی جزو طولانیترینها و پیچیدهترینها بوده. وقتی تا اینجا اومدی جلو یعنی شما بهترینی، مدتی هم میگذره تا شما با معجزهای که کردی بیشتر آشنا بشی و اون رو custom
خودتون کنید. مثلا eslint
بیارید، از Redux
استفاده کنید، از Redux-Saga
استفاده کنید، تست، مثل Jest
رو اضافه کنید و خیلی چیزهای باحال دیگه.
-- اجرا روی سرور واقعی
زمانی که همه کارهای بالا رو انجام دادید باید به نفر DevOps
تون بگید که روی سرور به pm2
نیاز دارید. چون برنامههای خفن React
ی که کاربرهای زیادی داره رو با دستور بالا روی سرور اصلی اجرا نمیکنن، برای این کار باید روی سرور اصلیتون دستور زیر رو اجرا کنید:
npm install pm2 -g
و برای اجرا:
NODE_ENV=production pm2 start ./express/production.js
البته بگما، این کار رو روی کامپیوتری که تا الآن هم استفاده کردید میتونید استفاده کنید، هیچ اشکالی نداره، ببینین که چه شکلیه! ولی این خروجی و نمایشتون روی مرورگر اصلا با
node ./express/production.js
فرقی نداره، تفاوتهاش رو نفر DevOps
میدونه که قراره مدیریت کش و لودبالانس و اینا اضافه کنه. اگر دوست داشتید میتونید که داک pm2
رو بخونید.
-- اختتام
برای راحتی کار دولوپ، من دستوراتی رو در قسمت scripts
در فایل package.json
گذاشتم که میتونید از آدرس گیتهاب که در بالای صفحه گذاشتم استفاده کنید یا همین زیر هم براتون مینویسم:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "NODE_ENV=development node ./express/development.js",
"build": "NODE_ENV=production webpack -p --config ./webpack/webpack.production.config.js --progress --profile --colors",
"prod": "NODE_ENV=production webpack -p --config ./webpack/webpack.production.config.js --progress --profile --colors && node ./express/production.js",
"pm2": "NODE_ENV=production pm2 start ./express/production.js"
}
یه توصیه آخر هم از من داشته باشید، درسته که تا الآن داشتیم با npm
کار میکردیم و الآن با تنظیم بالا مثلا برای شروع develop
از دستور npm run dev
استفاده میکنید ولی سرعت npm
خیلی کم هست و زمانی که شما در فایلهاتون تغییرات ایجاد میکنید تقریبا ۲۷ الی ۳۰ ثانیه طول میکشه که براتون سازه جدید رو بسازه و با Hard Reload
تغییرات رو ببینید. برای همین من توصیه میکنم که yarn
رو نصب کنید و با دستور yarn dev
شروع به develop
کنید، باورتون نمیشه سازه رو در زیر ۵۰۰ میلی ثانیه میسازه.
امیدوارم که تونسته باشم کمکتون کنم، همیشه موفق و شاد باشید.
مطلبی دیگر از این انتشارات
اجرای ریاکت سمت سرور (React SSR)
مطلبی دیگر از این انتشارات
بیاین بهتر React Native بنویسیم (2) - مسیر دهی کوتاه تر برای فراخوانی (همون Import خودمون)
مطلبی دیگر از این انتشارات
استفاده از فونت دلخواه در React Native