通用 React.JS - 需要样式和图像

Universal React.JS - Requiring Styles and Images

最近我一直在尝试 React 和 Webpack,到目前为止我一直很喜欢它。当然,它需要大量阅读、查看示例并尝试一些东西,但最终,反应本身以及热重新加载我的组件在我身上成长,我很确定我想继续这种方式。

在过去的几天里,尽管我一直在努力使我的渲染变得非常简单 "application"(到目前为止,它只是一个带有几个子组件的菜单和用于显示虚拟页面的反应路由器)渲染服务器端。

这是我的项目布局:

到目前为止,我的 webpack 配置如下所示:

let path = require("path"),
    webpack = require("webpack"),
    autoprefixer = require("autoprefixer");

module.exports = {
    entry: [
        "webpack-hot-middleware/client",
        "./client"
    ],
    output: {
        path: path.join(__dirname, "dist"),
        filename: "bundle.js",
        publicPath: "/static/"
    },
    resolve: {
        root: path.resolve(__dirname, "common")
    },
    module: {
        loaders: [
            { 
                test: /\.js$/, 
                loader: "babel", 
                exclude: /node_modules/,
                include: __dirname
            },
            { test: /\.scss$/, loader: "style!css!sass!postcss" },
            { test: /\.svg$/, loader: "file" }  
        ]
    },
    sassLoader: {
        includePaths: [path.resolve(__dirname, "common", "scss")]
    },
    plugins: [        
        new webpack.DefinePlugin({
            "process.env": {
                BROWSER: JSON.stringify(true),
                NODE_ENV: JSON.stringify( process.env.NODE_ENV || "development" )
            }
        }),
        new webpack.optimize.OccurenceOrderPlugin(),
        new webpack.HotModuleReplacementPlugin(),
        new webpack.NoErrorsPlugin()
    ],
    postcss: [autoprefixer({remove: false})]
}

并且,省略模块要求,我的服务器:

// Initialize express server
const server = express();

const compiler = webpack(webpackConfig);
server.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: webpackConfig.output.publicPath }));
server.use(webpackHotMiddleware(compiler));

// Get the request
server.use((req, res) => {
    // Use React Router to match our request with our components
    const location = createLocation(req.url);
    match({routes, location}, (error, redirectLocation, renderProps) => {
        if (error) {
            res.status(500).send(error.message);
        } else if (redirectLocation) {
            res.redirect(302, redirectLocation.pathname + redirectLocation.search);
        } else if (renderProps) {
            res.send(renderFullPage(renderProps));
        } else {
            res.status(404).send("Not found");
        }
    })

});

function renderFullPage(renderProps) {
    let html = renderToString(<RoutingContext {...renderProps}/>);
    return  `<!DOCTYPE html>
                <html lang="en">
                <head>
                    <meta charset="utf-8">
                    <title></title>
                </head>
                <body>
                    <div id="app">${html}</div>
                    <script src="/static/bundle.js"></script>
                </body>
                </html>
            `;
}

server.listen(3000);

这里有一些 React Router 服务器端的特殊性,但我可以理解它。

然后出现了一个问题,需要组件内部的样式。过了一会儿我想通了这一点(感谢 github 问题线程):

if (process.env.BROWSER) {
    require("../scss/components/menu.scss");
    logo = require("../images/flowychart-logo-w.svg") ;
}

除此之外,我仍然需要在我的客户端上声明一个路由器,重新使用我的服务器已经使用的路由:

// Need to implement the router on the client too
import React from "react";
import ReactDOM from "react-dom";
import { Router } from "react-router";
import createBrowserHistory from "history/lib/createBrowserHistory";
import routes from "../common/Routes";

const history = createBrowserHistory();

ReactDOM.render(
    <Router children={routes} history={history} />,
    document.getElementById("app")
);

现在我到了。应用了我的风格,一切正常。我仍然不是专家,但到目前为止我有点了解正在发生的一切(这是我不使用现有的通用 js / react 样板的主要原因 - 实际上了解正在发生的事情)。

但现在我要解决的下一个问题是如何在我的组件中要求我的图像。 使用此设置,它确实有效,但我收到此警告:

Warning: React attempted to reuse markup in a container but the 
checksum was invalid. 
This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. 
React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server:
     (client) "><img class="logo" src="/static/31d8270
     (server) "><img class="logo" alt=""

这是有道理的,正如您在前面的代码片段中看到的那样,我实际上在浏览器环境中设置了一个徽标变量,并且我使用该变量来设置我的组件的 src 标记。果然在服务器上,没有给出 src 属性,因此错误。

现在回答我的问题: 我做的那个样式/图像要求对吗?我缺少什么来摆脱那个警告?

我喜欢 webpack/react 关联的一件事是我如何使这些依赖关系接近使用它们的 UI 的实际部分。然而,我发现的一件事在大多数教程中都被忽略了,那就是如何在渲染服务器端时保持这种甜蜜的工作。 如果你们能给我一些见解,我将不胜感激。

非常感谢!

我推荐 webpack-isomorphic-tools 作为解决方案。

我有一节介绍如何在示例项目中将 SVG 导入 React 应用程序:https://github.com/peter-mouland/react-lego#importing-svgs

这是将我的 Universal React 应用程序变成接受 SVG 的应用程序所需的代码:https://github.com/peter-mouland/react-lego/compare/svg

您至少可以采用两种方法:

  1. 尝试 de-webpackify 你的代码到 运行 在 Node.
  2. 使用 target 'node'.
  3. 使用 Webpack 编译您​​的客户端代码

第一个选项要求您删除 Webpack 特定代码,例如 require.ensure / System.import 并教导节点您的模块解析规则以及如何将非 js 资产转换为 js。这可以通过一些 babel 转换和可能对 NODE_PATH 的修改来实现,但是这种方法确实意味着你一直在追逐你的尾巴,即如果你开始使用新的 Webpack 功能/加载器/插件,你需要确保你能够支持 N​​ode 中的等效功能。

第二种方法可能更实用,因为您可以确定您使用的任何 Webpack 功能也可以在服务器上运行。这是您可以使用的一种方法:

// webpack.config.js
module.exports = [
    {
        target: 'web',
        entry: './client',
        output: {
            path: dist,
            filename: 'client.js'
        }
    }, {
        target: 'node',
        entry: './server',
        output: {
            path: dist,
            filename: 'server.js',
            libraryTarget: 'commonjs2'
        }
    }
];

// client.js
renderToDom(MyApp, document.body);

// server.js
module.exports = (req, res, next) => {
    res.send(`
        <!doctype html>
        <html>
            <body>
                ${renderToString(MyApp)}`
                <script src="/dist/client.js"></script>
            </body>
        </html>
    `);
}

// index.js
const serverRenderer = require('./dist/server');
server.use(serverRenderer);
server.listen(6060);