SSR React - 在bundle.js加载

时间:2018-01-10 16:38:44

标签: node.js reactjs google-cloud-functions firebase-hosting ssr

我正在尝试使用Firebase托管和云功能创建SSR反应应用。我的组件使用className来声明类。我的服务器呈现的html不包含这些,它只有数据重新元素。

直到加载了bundle.js才会加载真正的 class =“example-class”

我不想在加载类之前等待bundle.js下载。我宁愿不用两者编码

class="kitten-image" className="kitten-image"

因为这似乎是浪费。我无法找到任何能够将CSS文件转换为具有数据重新识别标识符的内容,或者在编译过程中自动在服务器端包含 class =“kitten-image”的内容与巴贝尔。

概述:我的服务器端编译代码将babel编译的反应组件注入index.html模板文件,该文件通过 Firebase函数上的http请求通过express app发送。 index.html文件包含对 firebase hosting 公用文件夹中webpack处理的styles.css和bundle.js的硬编码引用。

因此,我的服务器端呈现的HTML应该能够立即引用styles.css表 - 但是,在加载bundle.js之前,这些类不在html中(这是问题)。

在bundle.js加载之前,服务器端呈现的HTML

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>React Server Side Rendering - Firebase Hosting</title>
    <link rel="stylesheet" href="styles.css">
   
  </head>
  <body>
    <div id="root"><div data-reactroot="" data-reactid="1" data-react-checksum="1473597379"><h1 data-reactid="2">Hello World!</h1><p data-reactid="3"><!-- react-text: 4 -->This is a kitten: <!-- /react-text --><br data-reactid="5"/><img src="/media/kitten.jpg" alt="Kitten" data-reactid="6"/></p></div></div>
    <script type="text/javascript" src="bundle.js"></script>
  </body>
</html>

在bundle.js加载后的HTML 请注意,已添加class =“kitten-image”。

<!DOCTYPE html>
<html><head>
    <meta charset="UTF-8">
    <title>React Server Side Rendering - Firebase Hosting</title>
    <link rel="stylesheet" href="styles.css">
   
  </head>
  <body>
    <div id="root"><div data-reactroot="" data-reactid="1"><h1 data-reactid="2">Hello World!</h1><p class="intro" data-reactid="3"><!-- react-text: 4 -->This is a kitten: <!-- /react-text --><br data-reactid="5"><img src="/media/kitten.jpg" alt="Kitten" class="kitten-image" data-reactid="6"></p></div></div>
    <script type="text/javascript" src="bundle.js"></script>
  

</body></html>

文件夹结构

folder-structure

应用组件示例 请参阅className

import React, { Component } from 'react';
import kitten from "./kitten.jpg";
import "./App.scss";

class App extends Component {
  render() {
    return (
      <div class="main">
        <h1>Hello World!</h1>
        <p className="intro">This is a kitten: <br /><img src={kitten} alt="Kitten" className="kitten-image" /></p>
      </div>
    );
  }
}

export default App;

Babel编译组件

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

var _react = require("react");

var _react2 = _interopRequireDefault(_react);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var kitten = "/media/kitten.jpg";

var App = function (_Component) {
  _inherits(App, _Component);

  function App() {
    _classCallCheck(this, App);

    return _possibleConstructorReturn(this, (App.__proto__ || Object.getPrototypeOf(App)).apply(this, arguments));
  }

  _createClass(App, [{
    key: "render",
    value: function render() {
      return _react2.default.createElement(
        "div",
        { "class": "main" },
        _react2.default.createElement(
          "h1",
          null,
          "Hello World!"
        ),
        _react2.default.createElement(
          "p",
          null,
          "This is a kitten: ",
          _react2.default.createElement("br", null),
          _react2.default.createElement("img", { src: kitten, alt: "Kitten" })
        )
      );
    }
  }]);

  return App;
}(_react.Component);

exports.default = App;

服务器index.js

import React from "react";
import { renderToString } from "react-dom/server";
import App from "../shared/App";
import express from "express";
import * as fs from "fs";
import * as functions from "firebase-functions";

const index = fs.readFileSync(__dirname + '/../../index.template.html', 'utf8');
const app = express();
app.get('**', (req, res) => {
  const html = renderToString(<App />);
  const finalHtml = index.replace('<!-- ::APP:: -->', html);
  res.set('Cache-Control', 'public, max-age=600, s-maxage=1200');
  res.send(finalHtml);
});

export let ssrapp = functions.https.onRequest(app);
//app.listen(3006, () => { console.log('Listening on 3006.'); });

服务器index.js babel编译

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.ssrapp = undefined;

var _react = require("react");

var _react2 = _interopRequireDefault(_react);

var _server = require("react-dom/server");

var _App = require("../shared/App");

var _App2 = _interopRequireDefault(_App);

var _express = require("express");

var _express2 = _interopRequireDefault(_express);

var _fs = require("fs");

var fs = _interopRequireWildcard(_fs);

var _firebaseFunctions = require("firebase-functions");

var functions = _interopRequireWildcard(_firebaseFunctions);

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

var index = fs.readFileSync(__dirname + '/../../index.template.html', 'utf8');
var app = (0, _express2.default)();
app.get('**', function (req, res) {
  var html = (0, _server.renderToString)(_react2.default.createElement(_App2.default, null));
  var finalHtml = index.replace('<!-- ::APP:: -->', html);
  res.set('Cache-Control', 'public, max-age=600, s-maxage=1200');
  res.send(finalHtml);
});

var ssrapp = exports.ssrapp = functions.https.onRequest(app);
//app.listen(3006, () => { console.log('Listening on 3006.'); });

的WebPack

const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const autoprefixer = require("autoprefixer");

const extractSass = new ExtractTextPlugin({
  filename: "public/styles.css",
  disable: process.env.NODE_ENV === "development"
});

// Webpack settings unique to browser-side script
const browserConfig = {
  entry: './src/browser/index.js',
  devtool: "source-map",
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        loader: 'babel-loader',
        exclude: /node_modules/
      },
      {
        test: [/\.svg$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
        loader: "file-loader",
        options: {
          name: "public/media/[name].[ext]",
          publicPath: url => url.replace(/public/, "")
        }
      },
      {
        test: /\.scss$/,
        use: extractSass.extract({
          use: [
            { loader: 'css-loader', options: { sourceMap: true } },
            { 
              loader: 'postcss-loader',
              options: {
                // Necessary for external CSS imports to work
                // https://github.com/facebookincubator/create-react-app/issues/2677
                ident: 'postcss',
                sourceMap: true,
                plugins: () => [
                  require('postcss-flexbugs-fixes'),
                  autoprefixer({
                    browsers: [
                      '>1%',
                      'last 4 versions',
                      'Firefox ESR',
                      'not ie < 9', // React doesn't support IE8 anyway
                    ],
                    flexbox: 'no-2009',
                  }),
                ],
              },
            },
            { loader: 'sass-loader', options: { sourceMap: true } }
          ],
          // use style-loader in development
          fallback: "style-loader"
        })
      }
    ]
  },
  plugins: [
    extractSass
  ],
  output: {
    filename: './public/bundle.js',
    path: __dirname
  }
}

module.exports = [browserConfig];

1 个答案:

答案 0 :(得分:1)

我最初使用的是Babel,因为我在使用Google Cloud / firebase模块通过webpack进行编译时出现问题。它试图不必要地捆绑所有东西。

我创建了一个单独的webpack服务器端配置。这样可以正常工作。

1.我正在使用&#34; webpack-node-externals&#34; 包,该包旨在排除节点模块以进行后端编译。没有这个,我生成的JS文件非常庞大。我的后端有一个完整的node_modules文件夹,因此它不需要捆绑这些项目。

2.我将虚假陈述添加到 __ dirname __ filename - 我不知道这是做什么或如何有效,但它解决了我的问题打开并阅读我的html模板文件服务器端。

3.文件加载器实际上并不复制文件, emit:false

  1. 真正的修复: .scss测试人员使用 css-loader / locals 这是关键!它在服务器上生成正确的类名,并在渲染时将它们放在组件中!它也不会捆绑/复制此配置中的文件,因为浏览器端配置会这样做。

    1. 我在 .babelrc 文件中使用以下插件和babel编译方法。这会破坏webpack中的图像/文件传输过程,必须从 .babelrc
    2. 中删除
  2. &#13;
    &#13;
      "plugins": [["transform-assets-import-to-string", {
        "baseDir": "",
        "baseUri": "/media"
      }]],
    &#13;
    &#13;
    &#13;

    修改了Webpack

    &#13;
    &#13;
    const serverConfig = {
      entry: "./src/server/index.js",
      target: "node",
      externals: [nodeExternals()], // exclude node_modules
      node: {
        __dirname: false,
        __filename: false
      },
      output: {
        filename: "./functions/src/server/server.js",
        libraryTarget: "commonjs2"
      },
      module: {
        rules: [
          {
            test: [/\.svg$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
            loader: "file-loader",
            options: {
              name: "public/media/[name].[ext]",
              publicPath: url => url.replace(/public/, ""),
              emit: false
            }
          },
          {
            test: /\.scss$/,
            use: [
              { loader: 'css-loader/locals' },
              { loader: 'sass-loader' }
            ]
          },
          {
            test: /\.(js|jsx)$/,
            exclude: /node_modules/,
            loader: "babel-loader"
          }
        ]
      }
    };
    &#13;
    &#13;
    &#13;