反应服务器端渲染(CSS模块)FOUC问题

时间:2017-04-17 09:09:38

标签: reactjs serverside-rendering css-modules

我有反应服务器端渲染的FOUC问题。我使用了React + ReactRouter + Redux 以下是我认为有必要列出的主要技术堆栈:

  • CSS Module处理组件样式
  • extract-text-webpack-plugin用于生成单独的css文件
  • css-modules-require-hook& asset-require-hook用于处理服务器端的资产

和我的webpack配置:

module.exports = {
   entry: [
   APP_PATH,
 ],

  output: {
     path: BUILD_PATH,
     filename: 'client.bundle.js',
     publicPath: '/JdBus/',
  },
 resolve: {
  extensions: ['.js', '.jsx'],
 },
 module: {
 rules: [
   {
    test: /\.(js|jsx)$/,
    exclude: /node_modules/,
    use: [
      'babel-loader',
    ],
  },

  {
    test: /\.css$/,
    use: ExtractTextPlugin.extract({
      fallback: 'style-loader',
      use: [
        {
          loader: 'css-loader',
          options: {
            modules: true, // 开启CSS Module
            localIdentName: '[name]__[local]-[hash:base64:5]',
          },
        },
        {
          loader: 'postcss-loader',
          options: {
            plugins() { // 这里配置postcss的插件
              return [autoprefixer];
            },
          },
        },
      ],
    }),
  },
  { // 处理图片
    test: /\.(png|jpg|gif|webp')$/,
    // include:path.resolve(__dirname,'/client/assets'),
    use: [{
      loader: 'url-loader',
      options: {
        limit: 10000,
        // 这个是输出的图片文件,跟output一致,生成的是bundleImg目录下的图片文件
        name: 'bundleImg/[hash:8].[name].[ext]',
      },
    }],
  },

  { // 处理文字
    test: /\.(woff|ttf|svg|eot|woff2)$/,
    // include:path.resolve(__dirname,'/client/assets'),
    use: [{
      loader: 'url-loader',
       options: {
        limit: 10000,
        name: 'bundleFonts/[hash:8]-[name].[ext]',
       },
     }],
    },
  ],
},
 plugins: [
    new webpack.DefinePlugin({
      'process.env': {
       NODE_ENV: JSON.stringify('production'),
      WEBPACK: true,
    },
   }),
   new webpack.optimize.UglifyJsPlugin({
     compressor: {
       warnings: false,
     },
   }),
  new ExtractTextPlugin('bundle.css'),
  new webpack.optimize.CommonsChunkPlugin({ 
    name: 'commons',
    filename: 'commons.js',
    minChunks: 2,
  }),
};

ExtractTextPlugin将在bundle.css中生成所有CSS样式。

我的服务器端处理快递:

match({ routes: AppRoutes, location: `/jdbus${req.url}` }, (err, redirectLocation, renderProps) => {
    if (err) {
      res.status(500).send(err.message);
    } else if (redirectLocation) {
      res.redirect(302, redirectLocation.pathname + redirectLocation.search);
    } else if (renderProps) {

      const store = configureStore({ user: {
        name: 'ddd',
        avatar: 'ddd',
      } });
      // console.log(renderProps);
      const marked = renderToStaticMarkup(
        <Provider store={store}>
            <RouterContext {...renderProps} />
        </Provider>
      );
      const initHtml = renderFullPage(marked, store.getState(), process.env.NODE_ENV);
      res.status(200).end(initHtml);
    } else {
      res.status(404).end('404 not');
    }
  });

renderFullPage()是一个从 veiw.js 生成html的函数:

function renderFullPage (html, initiaState, env) {
    // 根据生产和开发环境配置不同的页面
if (env === 'development') {
  return `
    <!DOCTYPE html>
      <html lang="en">
      <head>
      <title>开发测试页面</title>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-
      scale=1.0, user-scalable=no"/>
          <style>
          body{
              margin:0px;    
          }
          </style>
      </head>

      <body>
        <div id="root"></div>
        <script>
          window.__INITIAL_STATE__ = ${JSON.stringify(initiaState)};
        </script>
        <script src='/devClient.bundle.js'></script>
      </body>     
   </html>
   `;
  } else if (env === 'production') {
    return `
       <!DOCTYPE html>
       <html lang="en">
       <head>
       <meta charset="UTF-8">
       <title>十二棵橡树</title>
      <meta name="viewport" content="width=device-width, initial-scale=1.0, 
    user-scalable=no"/>
      <link rel="stylesheet" type="text/css" href="/JdBus/bundle.css">
      <style>
      body{
          margin:0px;
          scroll:no
      }
      </style>

    </head>
    <body>
      <div id="root">
          ${html}
      </div>
      <script>
        window.__INITIAL_STATE__ = ${JSON.stringify(initiaState)};
      </script>

    </body>
    </html>
  `;
   }
  }
 module.exports = renderFullPage;

我的 server.js 是程序开始的地方:

// 各种钩子
     require('babel-polyfill');
     require('css-modules-require-hook')({
         extensions: ['.css'],
         generateScopedName: '[name]__[local]-[hash:base64:8]',
    });

    require('asset-require-hook')({
        extensions: ['jpg', 'png', 'gif', 'webp'],
        limit: 10000,
    });
    // 处理字体
   require('asset-require-hook')({
      extensions: ['ttf', 'woff', 'svg', 'eot', 'woff2'],
      limit: 10000,
   });

const app = require('./app');

app.listen(PORT, () => {
  console.log('Node app is running on port:', PORT);

  // 注册全局未捕获异常处理器
  process.on('uncaughtException', (e) => {
    console.error(`Caught exception:, ${e.stack}`);
  });
  process.on('unhandledRejection', (reason, p) => {
    console.error(`Unhandled Rejection at: Promise , ${p},  reason: , ${reason.stack}`);
  });
});

asset-require-hookcss-modules-require-hook处理服务器端无法处理的资产。

除了...... FOUC,一切都还可以!我的服务器端会正常生成html,而每次尝试时都没有css。

这是我的截图: enter image description here

1 个答案:

答案 0 :(得分:0)

以下是我的webpack.config文件中的一个片段。我在React Server-side Rendering Example修正了FOUC问题。我认为它是在HtmlWebpackPlugin中使用HtmlWebpackHarddiskPlugin将alwaysWriteToDisk设置为true。

new ExtractTextPlugin({
    filename: isDevelopment
        ? 'assets/styles/main.css'
        : 'assets/styles/[name].[chunkhash].css',
}),

new HtmlWebpackPlugin({
    template: path.resolve(__dirname, 'src/index.html'),
    minify: isProduction ? {collapseWhitespace: true, collapseInlineTagWhitespace: true} : false,
    alwaysWriteToDisk: true,
}),
new HtmlWebpackHarddiskPlugin(),