我正在尝试使用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>
文件夹结构
应用组件示例 请参阅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];
答案 0 :(得分:1)
我最初使用的是Babel,因为我在使用Google Cloud / firebase模块通过webpack进行编译时出现问题。它试图不必要地捆绑所有东西。
我创建了一个单独的webpack服务器端配置。这样可以正常工作。
1.我正在使用&#34; webpack-node-externals&#34; 包,该包旨在排除节点模块以进行后端编译。没有这个,我生成的JS文件非常庞大。我的后端有一个完整的node_modules文件夹,因此它不需要捆绑这些项目。
2.我将虚假陈述添加到 __ dirname 和 __ filename - 我不知道这是做什么或如何有效,但它解决了我的问题打开并阅读我的html模板文件服务器端。
3.文件加载器实际上并不复制文件, emit:false
真正的修复: .scss测试人员使用 css-loader / locals 这是关键!它在服务器上生成正确的类名,并在渲染时将它们放在组件中!它也不会捆绑/复制此配置中的文件,因为浏览器端配置会这样做。
"plugins": [["transform-assets-import-to-string", {
"baseDir": "",
"baseUri": "/media"
}]],
&#13;
修改了Webpack
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;