یه برنامه نویس معمولی لینوکس کار
از React تا گوگل - قسمت سوم

خوب قسمت قبل رو که یادتونه؟
توی قسمت قبل من اومدم گفتم که از preRender استفاده کردم به اسم react-snap .
خداییش هم کارش خوبه . فقط چندتا مشکل داره .
مشکل اولش اینه که هرجا که clone کنم پروژه رو ، باید حجم زیادی chromless دانلود کنم . مشکل بعدی اینه که فرض کنین من چنین آدرسی توی سایتم دارم : site.com/ایران-الفبا-ری-اکت-ویرگول-نودجی-اس
خوب این آدرس طولانی و فارسیه . وقتی که اینکد میشه ( آخه react-snap میاد برای هر آدرس شما یک فولدر میسازه و کل سایت رو کرول میکنه و استاتیک تحویل میده . اسم هر فولدر هم میشه آدرس صفحه ای که توشه ) :
%D8%A7%DB%8C%D8%B1%D8%A7%D9%86-%D8%A7%D9%84%D9%81%D8%A8%D8%A7-%D8%B1%DB%8C-%D8%A7%DA%A9%D8%AA-%D9%88%DB%8C%D8%B1%DA%AF%D9%88%D9%84-%D9%86%D9%88%D8%AF%D8%AC%DB%8C-%D8%A7%D8%B3
خوب چنین فولدری نمیتونین بسازید . حداقل Nodejs با پلاگین mkdirp نمیسازه .
تا اینکه زیر پست دومی که گذاشتم ، دوستی اومد و لینک ویرگول خودش رو داد و راهنمایی کرد .
میتونین کامنتشو بخونین . این هم صفحه مطلب ویرگول ایشون در مورد SSR
خوب .
رفتم داخل مطلبشون .
اما من مشکل خیلی بدی داشتم .
- من از Material-ui استفاده کردم .
- لوکال استوریج و window رو نمیشه خارج از componentDidMount پارس کرد . چرا؟ چون که شما داخل server دسترسی به window و یا localStorage ندارید منطقا . چون این متغیرها ، برای مرورگر تعریف میشن ، نه سرور .
- پروژه زیاد بود حجمش و تقریبا 20 رو کد نویسی کرده بودم روش
- کار با وب پک کمی گنگ بود
- چجوری Deploy کنم؟
یکی یکی جواب اینارو میدم .
خوب Material-ui خودش گفته باید چی کارکنیم . این لینک رو بخونید :
خوب فقط این که نیست .
کلا چند مرحله هست که باید با وب پک انجام بشه :
بارگذاری css , بارگذاری js , بارگذاری assets مثل تصاویر و فونت ها , ایجاد یک وب سرور برای ارسال درخواست ها , ایجاد routing ها خوب خیلی اتفاقات بدی میفته ،که من فقط روی کد دوست عزیزمون مینویسم .
یعنی تغییراتیه که من اعمال کردم و کار کرد . ( شاید شما اعمال نکنید هم کار میکنه )
خوب این از Webpack
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: [
{
loader: 'babel-loader',
test: /\.(js|jsx)$/,
exclude: /(node_modules\/)/,
query: {
presets: ['react', 'es2015'],
plugins: ['transform-class-properties'],
"env": {
// only enable it when process.env.NODE_ENV is 'development' or undefined
"development": {
"plugins": [["react-transform", {
"transforms": [{
"transform": "react-transform-hmr",
// if you use React Native, pass "react-native" instead:
"imports": ["react"],
// this is important for Webpack HMR:
"locals": ["module"]
}]
// note: you can put more transforms into array
// this is just one of them!
}]]
}
}
}
},
{
test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
use: [{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'fonts/'
}
}]
},
{
test: /\.(pdf|jpg|png|gif|svg|ico)$/,
use: [
{
loader: 'url-loader'
},
]
}
,
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
],
},
plugins: [
new ExtractTextPlugin({
filename: 'style.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: [
{
loader: 'babel-loader',
test: /\.(js|jsx)$/,
exclude: /(node_modules\/)/,
query: {
presets: ['react', 'es2015'],
plugins: ['transform-class-properties'],
"env": {
// only enable it when process.env.NODE_ENV is 'development' or undefined
"development": {
"plugins": [["react-transform", {
"transforms": [{
"transform": "react-transform-hmr",
// if you use React Native, pass "react-native" instead:
"imports": ["react"],
// this is important for Webpack HMR:
"locals": ["module"]
}]
// note: you can put more transforms into array
// this is just one of them!
}]]
}
}
}
},
{
test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
use: [{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'fonts/'
}
}]
},
{
test: /\.(pdf|jpg|png|gif|svg|ico)$/,
use: [
{
loader: 'url-loader'
},
]
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
],
},
plugins: [
new ExtractTextPlugin({
filename: 'style.css',
allChunks: true
})
]
}
];
خیلی از کدها رو دوست عزیزمون نوشتن . من تغییراتی دادم داخل بارگذاری تصاویر و فونت ها و محیط production در بخش js .
حالا میرسیم به رفع مشکلات window و localstorage.
من توی بخش production.js تغییرات زیادی دادم تا تونستم استفاده کنم . یک سری پلاگین ها نصب کردم و مشکلم رفع شد . البته تا پیدا کردم و فهمیدم چی به چیه ، زمان برد .
const express = require('express');
const path = require('path');
const app = express();
const localStorage=require('localStorage')
const Window = require('window');
const { document } = new Window();
global.window = new Window();
global.document = document;
global.navigator=window.navigator;
global.localStorage=localStorage;
require('matchmedia-polyfill');
require('matchmedia-polyfill/matchMedia.addListener');
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 = 2020;
app.listen(PORT, error => {
if (error) {
return console.error(error);
} else {
console.log(`Production Express server running at http://localhost:${PORT}`);
}
});
یک پلاگین هم نصب کردم و داخل کدهای اصلی پروژه ، هرجا window داشتم این رو گذاشتم . به این صفحه برید و ببینید چقدر باحاله :
طبق گقته دوستمون برای نگه داری و Deploy از pm2 استفاده کردم و توصیه میکنم شما هم اینکارو کنید .
کار باهاش خیلی جذابه . اگر کسی با داکر و docker-machine کار کرده باشه ، بعضی از کارایی های pm2 رو به خوبی میشناسه . مثل بخش cluster کردنش .
آخرین نکنته اینکه ، ممکنه development و production با هم تفاوت داشتن باشن موقع کار . به همین دلیل من هر دو رو یکی کردم توی webpack تا خروجی یکسان داشته باشم .
اگر با pm2 کار میکنین حتما اول npm run prod رو بزنید و بعدش pm2 reload . اینجوری نیاز نیست که pm2 رو kill کنید .
با مراحل بالا من کامل تونستم ssr کنم و از نتیجه و پرفورمنس خیلی راضیم .
نسبت به مقاله اولی که در مورد ری اکت و گوگل نوشتم ، خیلی تغییر کردم و معلوماتم تغییر کرد .
صد در صد در مورد این مقاله هم همینه .
یک مورد هم یادم رفت . اینکه server.js هم باید تغییر میدادم برای متریال .
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import {StaticRouter} from 'react-router-dom';
import {Helmet} from "react-helmet";
import App from './App';
import JssProvider from 'react-jss/lib/JssProvider';
import { SheetsRegistry} from 'jss';
import Template from './Template';
import {
MuiThemeProvider,
createMuiTheme,
createGenerateClassName,
} from '@material-ui/core/styles';
Helmet.canUseDOM = false;
export default function serverRenderer({clientStats, serverStats}) {
return (req, res, next) => {
const sheetsRegistry = new SheetsRegistry();
// Create a sheetsManager instance.
const sheetsManager = new Map();
// Create a theme instance.
const theme = createMuiTheme({
direction: 'rtl',
typography: {
"fontFamily": "IRYekan",
},
palette: {
primary: {
light: '#B0BEC5',
main: '#78909C',
dark: '#212121',
contrastText: '#ffffff',
},
secondary: {
light: '#F8BBD0',
// main: '#F06292',
// dark: '#C2185B',
main: '#555555',
dark: '#aa2712',
contrastText: '#ffffff',
}
},
overrides: {
MuiBottomNavigation: {
root: { // Name of the rule
bottom: 0,
left: 0,
right: 0,
// position: "sticky",
position: "fixed",
boxShadow: "0px 2px 4px -1px rgba(0, 0, 0, 0.2), 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12)"
},
},
MuiBottomNavigationAction: {
root: { // Name of the rule
minWidth: "auto",
// left: 0,
// right: 0,
// position: "fixed",
},
},
MuiAvatar: {
root: {
marginRight: 'auto',
marginLeft: '16px',
borderRadius: '2px'
}
},
MuiCardHeader: {
action: {
marginRight: "auto",
marginLeft: "-16px"
},
avatar: {
marginRight: "auto",
marginLeft: "16px"
}
},
MuiExpansionPanelSummary: {
root: {
background: "rgba(0,0,0,.05)"
},
expandIcon: {
left: "8px",
right: "auto",
},
content: {
display: 'flex',
alignItems: 'center',
margin: 0
},
expanded: {
margin: "0 !important"
}
},
MuiExpansionPanelDetails: {
root: {
padding: 0
}
},
MuiList: {
padding: {
padding: '0!important'
}
},
MuiListItem: {
root: {
textAlign: "right"
}
},
MuiListItemText: {
root: {
padding: 0
}
},
MuiTableCell: {
root: {
padding: "2px",
textAlign: 'center'
}
},
MuiListItemIcon: {
root: {
marginRight: 'auto',
marginLeft: '16px'
}
},
MuiFormControlLabel: {
root: {
marginLeft: '16px',
marginRight: '-14px'
}
}
}
});
// Create a new class name generator.
const generateClassName = createGenerateClassName();
const context = {};
const markup = ReactDOMServer.renderToString(
<StaticRouter location={req.url} context={context}>
<JssProvider registry={sheetsRegistry} generateClassName={generateClassName}>
<MuiThemeProvider theme={theme} sheetsManager={sheetsManager}>
<App/>
</MuiThemeProvider>
</JssProvider>
</StaticRouter>
);
const helmet = Helmet.renderStatic();
const css = sheetsRegistry.toString();
res.status(200).send(Template({
markup: markup,
helmet: helmet,
css : css
}));
};
}
بخشی که نوشتم canUseDom=false مرتبط با این ایشو هست :
باقی بخش ها هم تنظیمات متریال هست که داخل سایتش گفته
حتما با راه کارهای بهتر و اتفاقات جالبتر میام و مجدد تجربیاتم رو به اشتراک میزارم .
در آخر اگر مطلبم مفید بود یک فنجون قهوه مهمونم کن . حرف زیاد داریم بزنیم .
مطلبی دیگر از این انتشارات
حذف Rerender های اضافی در کامپوننت های React
مطلبی دیگر از این انتشارات
اجرای ریاکت سمت سرور (React SSR)
مطلبی دیگر از این انتشارات
دنیای جدید reactJS