У меня есть вопрос о рендеринге на стороне сервера с пользовательским интерфейсом материалов и, в частности, о системных функциях (@ material-ui / system).
В моем приложении ReactJS у меня есть следующий фрагмент кода (я используюСистемные реквизиты Flexbox для Material-ui в этом примере):
<Box display="flex">Hello</Box>
Когда сервер отправляет мне HTML-страницу, атрибут «display» присутствует в исходном коде, но когда гидратация выполняетсяклиент, я не нахожу в DOM свойство "display: flex" для этого компонента.
Однако, когда я использую атрибут 'style', я нахожу это свойство в DOM.
<Box style={{display:'flex'}}>Hello</Box>
Я хочу пояснить, что он не работает, когда я импортирую свой компонент Box таким образом:
import Box from '@material-ui/core/Box';
С другой стороны, когда я импортирую его с помощью Styled-Component, он работает:
import styled from 'styled-components';
const Box = styled.div`${display}`;
Для получения дополнительной информации здесь приведена конфигурация моего сервера и моего клиента.
server.js
import express from 'express';
import fs from 'fs';
import Render from './Render';
import { resolveAppPath } from '../utils/resolve-app-path';
import { conf } from '../../i18n';
const middleware = require('i18next-express-middleware');
const i18next = require('i18next');
const dev = process.env.NODE_ENV === 'development';
import devMiddleware from './dev-middleware';
const app = express();
const port = process.env.PORT || 80;
if (dev) {
devMiddleware(app);
}
app.use('/assets', express.static(resolveAppPath('dist/assets')));
i18next.use(middleware.LanguageDetector).init(conf);
app.use(middleware.handle(i18next));
app.get('*', (req, res) => {
const index = resolveAppPath('dist/assets/index.html');
fs.readFile(index, 'utf8', (err, data) => {
if (err) {
console.error('error when reading index file : ', err);
res.status(500).send('error when reading index file');
}
res.status(200).send(Render(req, data));
});
});
app.listen(port, () => {
console.log('Server started on port : ' + port);
});
Render.js
import ReactDOMServer from 'react-dom/server';
import App from '../shared/components/App';
import React from 'react';
import { ServerStyleSheets } from '@material-ui/core/styles';
import { ServerStyleSheet } from 'styled-components';
import { I18nextProvider } from 'react-i18next';
import { StaticRouter } from 'react-router-dom';
const Render = (req, data) => {
const context = {};
const muiSheet = new ServerStyleSheets();
const scSheet = new ServerStyleSheet();
try {
const html = ReactDOMServer.renderToString(
muiSheet.collect(
scSheet.collectStyles(
<StaticRouter location={req.url} context={context}>
<I18nextProvider i18n={req.i18n}>
<App />
</I18nextProvider>
</StaticRouter>,
),
),
);
const muiCss = muiSheet.toString();
const scCss = scSheet.getStyleTags();
data = data.replace('<div id="root"></div>', `<div id="root">${html}</div>`);
data = data.replace('<style id="jss-server-side"></style>', `<style id="jss-server-side">${muiCss}</style>`);
data = data.replace('</head>', `${scCss}</head>`);
} catch (error) {
console.error(error);
} finally {
scSheet.seal();
}
return data;
};
client.js
import React from 'react';
import { hydrate, render } from 'react-dom';
import App from 'Components/App';
import { I18nextProvider } from 'react-i18next';
import i18n from '../../i18n';
import { BrowserRouter } from 'react-router-dom';
import { AppContainer } from 'react-hot-loader';
function Main() {
React.useEffect(() => {
const jssStyles = document.querySelector('#jss-server-side');
if (jssStyles) {
jssStyles.parentNode.removeChild(jssStyles);
}
}, []);
return <App />;
}
const renderApp = Component => {
hydrate(
<AppContainer>
<BrowserRouter>
<I18nextProvider i18n={i18n}>
<Component />
</I18nextProvider>
</BrowserRouter>
</AppContainer>,
document.getElementById('root'),
);
};
renderApp(Main);
if (module.hot) {
module.hot.accept('Components/App', () => {
renderApp(Main);
});
}
App.js
import React from 'react';
import CssBaseline from '@material-ui/core/CssBaseline';
import { ThemeProvider } from 'styled-components';
import { ThemeProvider as ThemeProviderMUI, StylesProvider } from '@material-ui/core/styles';
import theme from '../theme';
import Box from '@material-ui/core/Box';
const App = () => {
return (
<div>
<CssBaseline />
<StylesProvider injectFirst>
<ThemeProviderMUI theme={theme}>
<ThemeProvider theme={theme}>
<Box display="flex"></Box>
</ThemeProvider>
</ThemeProviderMUI>
</StylesProvider>
</div>
);
};
export default App;
index.html
<!DOCTYPE html>
<html lang="fr">
<head>
<base href="/" />
<title>Mario Laporte - Développeur web</title>
<meta name="description" content="Mario Laporte - Développeur web" />
<meta charset="UTF-8" />
<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no" />
<meta name="mobile-web-app-capable" content="yes" />
<!--
# List of devices and resolutions (AUG-2019):
#
# Device Portrait size Landscape size Screen size Pixel ratio
# iPhone SE 640px × 1136px 1136px × 640px 320px × 568px 2
# iPhone 8 750px × 1334px 1334px × 750px 375px × 667px 2
# iPhone 7 750px × 1334px 1334px × 750px 375px × 667px 2
# iPhone 6s 750px × 1334px 1334px × 750px 375px × 667px 2
# iPhone XR 828px × 1792px 1792px × 828px 414px × 896px 2
# iPhone XS 1125px × 2436px 2436px × 1125px 375px × 812px 3
# iPhone X 1125px × 2436px 2436px × 1125px 375px × 812px 3
# iPhone 8 Plus 1242px × 2208px 2208px × 1242px 414px × 736px 3
# iPhone 7 Plus 1242px × 2208px 2208px × 1242px 414px × 736px 3
# iPhone 6s Plus 1242px × 2208px 2208px × 1242px 414px × 736px 3
# iPhone XS Max 1242px × 2688px 2688px × 1242px 414px × 896px 3
# 9.7" iPad 1536px × 2048px 2048px × 1536px 768px × 1024px 2
# 7.9" iPad mini 4 1536px × 2048px 2048px × 1536px 768px × 1024px 2
# 10.5" iPad Pro 1668px × 2224px 2224px × 1668px 834px × 1112px 2
# 11" iPad Pro 1668px × 2388px 2388px × 1668px 834px × 1194px 2
# 12.9" iPad Pro 2048px × 2732px 2732px × 2048px 1024px × 1366px 2
-->
<link rel="apple-touch-startup-image" href="icons/ios/startup/portrait/0640x1136_icon.png" media="(min-device-width: 320px) and (max-device-width: 568px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/portrait/0750x1334_icon.png" media="(min-device-width: 375px) and (max-device-width: 667px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/portrait/0828x1792_icon.png" media="(min-device-width: 414px) and (max-device-width: 896px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/portrait/1125x2436_icon.png" media="(min-device-width: 375px) and (max-device-width: 812px) and (-webkit-min-device-pixel-ratio: 3) and (orientation: portrait)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/portrait/1242x2208_icon.png" media="(min-device-width: 414px) and (max-device-width: 736px) and (-webkit-min-device-pixel-ratio: 3) and (orientation: portrait)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/portrait/1242x2688_icon.png" media="(min-device-width: 414px) and (max-device-width: 896px) and (-webkit-min-device-pixel-ratio: 3) and (orientation: portrait)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/portrait/1536x2048_icon.png" media="(min-device-width: 768px) and (max-device-width: 1024px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/portrait/1668x2224_icon.png" media="(min-device-width: 834px) and (max-device-width: 1112px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/portrait/1668x2388_icon.png" media="(min-device-width: 834px) and (max-device-width: 1194px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/portrait/2048x2732_icon.png" media="(min-device-width: 1024px) and (max-device-width: 1366px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/landscape/1136x0640_icon.png" media="(min-device-width: 320px) and (max-device-width: 568px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: landscape)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/landscape/1334x0750_icon.png" media="(min-device-width: 375px) and (max-device-width: 667px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: landscape)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/landscape/1792x0828_icon.png" media="(min-device-width: 414px) and (max-device-width: 896px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: landscape)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/landscape/2436x1125_icon.png" media="(min-device-width: 375px) and (max-device-width: 812px) and (-webkit-min-device-pixel-ratio: 3) and (orientation: landscape)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/landscape/2208x1242_icon.png" media="(min-device-width: 414px) and (max-device-width: 736px) and (-webkit-min-device-pixel-ratio: 3) and (orientation: landscape)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/landscape/2688x1242_icon.png" media="(min-device-width: 414px) and (max-device-width: 896px) and (-webkit-min-device-pixel-ratio: 3) and (orientation: landscape)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/landscape/2048x1536_icon.png" media="(min-device-width: 768px) and (max-device-width: 1024px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: landscape)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/landscape/2224x1668_icon.png" media="(min-device-width: 834px) and (max-device-width: 1112px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: landscape)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/landscape/2388x1668_icon.png" media="(min-device-width: 834px) and (max-device-width: 1194px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: landscape)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/landscape/2732x2048_icon.png" media="(min-device-width: 1024px) and (max-device-width: 1366px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: landscape)" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
<style id="jss-server-side"></style>
</head>
<body>
<div id="root"></div>
<noscript>
Vous devez activer JavaScript pour exécuter cette application.
</noscript>
</body>
</html>
Знаете ли вы, нормальное ли это поведение? Системные функции пользовательского интерфейса материалов не совместимы с рендерингом на стороне сервера?
Не думаю, что проблема связана с моей конфигурацией, поскольку у меня нет проблем, если я не использую системные функции.
Такжерендеринг выполняется с помощью свойства dom в среде разработки, но не в производственной среде. Это очень странноЯ не могу найти ответы на этот вопрос, у вас может быть идея?
Я также свернул конфигурацию своего веб-пакета, вот что я использую:
webpack.common.config.js
const ImageminPlugin = require('imagemin-webpack-plugin').default;
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebPackPlugin = require('html-webpack-plugin');
const { resolveAppPath } = require('../../utils/resolve-app-path');
module.exports = {
entry: ['babel-polyfill', './src/client'],
output: { path: resolveAppPath('dist/assets'), filename: '[name].js', publicPath: '/assets' },
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
alias: {
Components: resolveAppPath('src/shared/components'),
Src: resolveAppPath('src'),
Hooks: resolveAppPath('src/hooks'),
Node: resolveAppPath('node_modules'),
Img: resolveAppPath('public/images'),
'react-dom': '@hot-loader/react-dom',
},
},
module: {
rules: [
{ test: /\.(ts|js)x?$/, exclude: /node_modules/, loader: ['babel-loader', 'eslint-loader'] },
{ test: /\.css$/, use: ['style-loader', 'css-loader'] },
{ test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] },
{ test: /\.(png|jpg|gif)$/, use: ['file-loader'] },
],
},
plugins: [
new HtmlWebPackPlugin({
template: resolveAppPath('public/index.html'),
favicon: resolveAppPath('public/favicon.ico'),
}),
new CopyWebpackPlugin([
{
from: resolveAppPath('public/images/icons/ios/startup'),
to: resolveAppPath('dist/assets/icons/ios/startup'),
},
]),
new ImageminPlugin({
pngquant: {
speed: 11,
quality: 100,
},
cacheFolder: resolveAppPath('cache'),
}),
],
};
webpack.dev.config.js
const webpack = require('webpack');
const { resolveAppPath } = require('../../utils/resolve-app-path');
module.exports = {
entry: ['react-hot-loader/patch', 'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000'],
mode: 'development',
target: 'web',
devtool: 'eval-source-map',
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
},
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('development'),
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
],
};
webpack.prod.config.js
const webpack = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
mode: 'production',
target: 'web',
plugins: [
new CleanWebpackPlugin(),
new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') }),
],
};
Большое спасибо, что нашли время, чтобы ответить мне.