یه برنامه نویس معمولی لینوکس کار
از 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 مرتبط با این ایشو هست :
باقی بخش ها هم تنظیمات متریال هست که داخل سایتش گفته
حتما با راه کارهای بهتر و اتفاقات جالبتر میام و مجدد تجربیاتم رو به اشتراک میزارم .
در آخر اگر مطلبم مفید بود یک فنجون قهوه مهمونم کن . حرف زیاد داریم بزنیم .
مطلبی دیگر از این انتشارات
چطور logic پروژمون رو بین React و React Native باید share کنیم؟
مطلبی دیگر از این انتشارات
ری اکت رو قورت بده - ۲
مطلبی دیگر از این انتشارات
ری اکت نیتیو و گوشی های فارسی زبان