Universal React.JS - 需要样式和图像

时间:2016-01-26 23:12:31

标签: javascript reactjs webpack server-side universal

最近我一直在涉及React和Webpack,到目前为止我一直很喜欢它。当然需要大量的阅读,看看例子,并尝试一些东西,但最终,自我反应以及热重新加载我的组件在我身上发展,我很确定我想继续这样。

在过去的几天里,虽然我一直在尝试制作一个非常简单的应用程序" (到目前为止它只是一个带有几个子组件的菜单,并且反应路由器显示虚拟页面)渲染服务器端。

这是我项目的布局:

enter image description here

这是我的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);

Little 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样板的主要原因 - 实际上理解了什么&# 39; s继续)。

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

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的实际部分附近。然而,大多数教程都忽略了我发现的一件事,那就是在渲染服务器端时如何保持这种甜味。 如果你们能给我一些见解,我将非常感激。

非常感谢!

2 个答案:

答案 0 :(得分:2)

我会建议使用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 :(得分:1)

你可以采取至少两种方法:

  1. 尝试将您的代码解包,以便在Node中运行。
  2. 使用target 'node'编译您的客户端代码与Webpack。
  3. 第一个选项要求您删除Webpack特定代码,例如require.ensure / System.import和教您节点模块解析规则以及如何将非js资产转换为js。这可以通过一些babel转换和NODE_PATH的修改来实现,但是这种方法确实意味着你经常追逐你的尾巴,即如果你开始使用新的Webpack特性/加载器/插件,你需要确保你'能够支持Node中的等效功能。

    第二种方法可能更实用,因为您可以确定您使用的任何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);