ReactJS / Material-ui - Стилизованные функции системы и стиля в SSR - PullRequest
0 голосов
/ 20 ноября 2019

У меня есть вопрос о рендеринге на стороне сервера с пользовательским интерфейсом материалов и, в частности, о системных функциях (@ 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') }),
    ],
};

Большое спасибо, что нашли время, чтобы ответить мне.

...