jaredpalmer / razzle

✨ Create server-rendered universal JavaScript applications with no configuration
https://razzlejs.org
MIT License
11.1k stars 866 forks source link

A possible hapi.js example #592

Closed SamuelEarl closed 6 years ago

SamuelEarl commented 6 years ago

hapi.js is a great Node framework and v17 is fully async/await.

I don't understand all of the inner workings for Razzle, so I am not sure how to configure a hapi server to work properly, but here is a possible starting point that seems to work in place of the Express server:

import Path from 'path';
import App from './App';
import React from 'react';
import { StaticRouter } from 'react-router-dom';
import Hapi from 'hapi';
import Inert from 'inert';
import { renderToString } from 'react-dom/server';

const assets = require(process.env.RAZZLE_ASSETS_MANIFEST);

const server = new Hapi.Server({
  port: 4000
});

(async () => {
  try {
    await server.register(Inert);

    // I am not sure if this route serves the public folder correctly,
    // but this route doesn't seem necessary if you are using CSS in JS
    // or if you are importing your images into your components
    await server.route({
      method: 'GET',
      path: process.env.RAZZLE_PUBLIC_DIR + '/{file*}',
      handler: {
        directory: {
          path: process.env.RAZZLE_PUBLIC_DIR
        }
      }
    });

    await server.route({
      method: 'GET',
      path: '/{path*}',
      options: {
        async handler(request, h) {
          try {
            const context = {};
            const markup = renderToString(
              <StaticRouter context={context} location={request.url}>
                <App />
              </StaticRouter>
            );

            if (context.url) {
              return h.redirect(context.url);
            }
            else {
              const template = `
                <!doctype html>
                <html lang="">
                <head>
                  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
                  <meta charset="utf-8" />
                  <title>Welcome to Razzle</title>
                  <meta name="viewport" content="width=device-width, initial-scale=1">
                  ${assets.client.css
                  ? `<link rel="stylesheet" href="${assets.client.css}">`
                  : ''}
                  ${process.env.NODE_ENV === 'production'
                  ? `<script src="${assets.client.js}" defer></script>`
                  : `<script src="${assets.client.js}" defer crossorigin></script>`}
                </head>
                <body>
                  <div id="root">${markup}</div>
                </body>
                </html>`

              return h.response(template).code(200);
            }
          }
          catch (err) {
            console.log(err);
          }
        }
      }
    });

    await server.start();
  }
  catch (err) {
    console.log(err);
  }
}) ();

This seems to work as a drop-in replacement for the Express server code (just copy and paste over the code in the server.js file), although I haven't tested it too much.

Also, I would like to know if there is a way to change the organization of the files in Razzle? I prefer to separate all of my client and server code into their own folders (e.g., src/client/ and src/server/).

Thank you!

stale[bot] commented 6 years ago

Hola! So here's the deal, between open source and my day job and life and what not, I have a lot to manage, so I use a GitHub bot to automate a few things here and there. This particular GitHub bot is going to mark this as stale because it has not had recent activity for a while. It will be closed if no further activity occurs in a few days. Do not take this personally--seriously--this is a completely automated action. If this is a mistake, just make a comment, DM me, send a carrier pidgeon, or a smoke signal.

stale[bot] commented 6 years ago

ProBot automatically closed this due to inactivity. Holler if this is a mistake, and we'll re-open it.

SamuelEarl commented 6 years ago

How can I get this added in the examples directory?

zpetukhov commented 4 years ago

static route in the example above doesn't work. below is working example (replace 'static' to your value if it differs):

    await server.route({
      method: 'GET',
      path: '/static/{file*}',
      handler: {
        directory: {
          path: process.env.RAZZLE_PUBLIC_DIR + '/static'
        }
      }
    });

note: process.env.RAZZLE_PUBLIC_DIR is a full path in the filesystem. note2: robots.txt and favicon.ico isn't served by that route (they are not nested in /static)

karanmartian commented 3 years ago

Hi I was able to integrate hapi with latest razzle version, but cannot find a way on how to auto-reload the server. Could you please help?

karanmartian commented 3 years ago

Want to just add here for anyone who would find this in 2200 AD

Change request.url to request.url.pathname

karanmartian commented 3 years ago

This is the full correct HapiJS Server with Redux! Replace CONFIG stuff with your stuff

import * as Hapi from "@hapi/hapi";
import * as Inert from "@hapi/inert";
import * as Vision from "@hapi/vision";
import React from "react";
import { renderToString } from "react-dom/server";
import Helmet from "react-helmet";
import { Provider } from "react-redux";
import { StaticRouter } from "react-router-dom";
import serialize from "serialize-javascript";
import CONFIG from "../configs/gconfig";
import App from "./App";
import configureStore from "./configureStore";

// prettier-ignore
const regex = /[^\\]*\.(\w+)$/;
const assets = require(process.env.RAZZLE_ASSETS_MANIFEST);

const cssLinksFromAssets = (assets, entrypoint) => {
    return assets[entrypoint]
        ? assets[entrypoint].css
            ? assets[entrypoint].css
                  .map((asset) => `<link rel="stylesheet" href="${asset}">`)
                  .join("")
            : ""
        : "";
};

const jsScriptTagsFromAssets = (assets, entrypoint, extra = "") => {
    return assets[entrypoint]
        ? assets[entrypoint].js
            ? assets[entrypoint].js
                  .map((asset) => `<script src="${asset}"${extra}></script>`)
                  .join("")
            : ""
        : "";
};

const ser = async () => {
    const plugins = [
        {
            plugin: Inert,
        },
        {
            plugin: Vision,
        },
    ];

    const server = Hapi.server({
        port: process.env.PORT ? parseInt(process.env.PORT, 10) : 3000,
        host: "localhost",
    });

    try {
        await server.register(plugins);
    } catch (err) {
        console.log(err);
    }

    // Static Files Generated by Razzle
    await server.route({
        method: "GET",
        path: "/static/{file*}",
        handler: {
            directory: {
                path: process.env.RAZZLE_PUBLIC_DIR + "/static",
            },
        },
    });

    // Test Route
    server.route({
        method: "GET",
        path: "/test",
        async handler(request, h) {
            return { r: "kar" };
        },
    });

    // server.route(routes);

    // Serve Catch All Route or Server File with Regex
    server.route({
        method: "GET",
        path: "/{path*}",
        options: {
            async handler(request, h) {
                try {
                    const total = request.path.match(regex);

                    if (total) {
                        return h.file(
                            process.env.RAZZLE_PUBLIC_DIR + request.path
                        );
                    } else {
                        const context = {};

                        // Create Redux store, and get initial state.
                        const store = configureStore({});

                        const markup = renderToString(
                            <Provider store={store}>
                                <StaticRouter
                                    context={context}
                                    location={request.url.pathname}
                                >
                                    <App />
                                </StaticRouter>
                            </Provider>
                        );

                        const finalState = store.getState();
                        // @ts-expect-error
                        if (context.url) {
                            // @ts-expect-error
                            return h.redirect(context.url);
                        } else {
                            const helmet = Helmet.renderStatic();
                            const html =
                                // prettier-ignore
                                `<!doctype html>
                                <html lang="en-us">
                                    <head>
                                        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
                                        <meta charSet='utf-8' />
                                        <meta name="viewport" content="width=device-width, initial-scale=1">
                                        ${helmet.title}
                                        ${helmet.meta}
                                        <meta name="keywords" content="${CONFIG["keywords"]}">
                                        <meta name="theme-color" content="#ffffff">
                                        <meta property="og:type" content="website" />
                                        <meta property="og:url" content="${CONFIG["domain"]}" />
                                        <meta property="og:site_name" content="${CONFIG["brand"] + ", " + CONFIG["brand_name"]}" />
                                        <meta property="og:image" content="${CONFIG["og"].concat("og.jpg")}" />
                                        <meta property="og:locale" content="en_US" />
                                        ${helmet.link.toString()}
                                        <link rel="apple-touch-icon" sizes="57x57" href=${CONFIG["favicon"].concat("apple-icon-57x57.png")}>
                                        <link rel="apple-touch-icon" sizes="60x60" href=${CONFIG["favicon"].concat("apple-icon-60x60.png")}>
                                        <link rel="apple-touch-icon" sizes="72x72" href=${CONFIG["favicon"].concat("apple-icon-72x72.png")}>
                                        <link rel="apple-touch-icon" sizes="76x76" href=${CONFIG["favicon"].concat("apple-icon-76x76.png")}>
                                        <link rel="apple-touch-icon" sizes="114x114" href=${CONFIG["favicon"].concat("apple-icon-114x114.png")}>
                                        <link rel="apple-touch-icon" sizes="120x120" href=${CONFIG["favicon"].concat("apple-icon-120x120.png")}>
                                        <link rel="apple-touch-icon" sizes="144x144" href=${CONFIG["favicon"].concat("apple-icon-144x144.png")}>
                                        <link rel="apple-touch-icon" sizes="152x152" href=${CONFIG["favicon"].concat("apple-icon-152x152.png")}>
                                        <link rel="apple-touch-icon" sizes="180x180" href=${CONFIG["favicon"].concat("apple-icon-180x180.png")}>
                                        <link rel="icon" type="image/png" sizes="192x192"  href=${CONFIG["favicon"].concat("android-icon-192x192.png")}>
                                        <link rel="icon" type="image/png" sizes="32x32" href=${CONFIG["favicon"].concat("favicon-32x32.png")}>
                                        <link rel="icon" type="image/png" sizes="96x96" href=${CONFIG["favicon"].concat("favicon-96x96.png")}>
                                        <link rel="icon" type="image/png" sizes="16x16" href=${CONFIG["favicon"].concat("favicon-16x16.png")}>
                                        <link rel="manifest" href=${CONFIG["favicon"].concat("manifest.json")}>
                                        <meta name="msapplication-TileColor" content="#ffffff">
                                        <meta name="msapplication-TileImage" content=${CONFIG["favicon"].concat("ms-icon-144x144.png")}>
                                        ${cssLinksFromAssets(assets, 'client')}
                                    </head>
                                    <body>
                                        <div id="root">${markup}</div>
                                        <script>
                                            window.__PRELOADED_STATE__ = ${serialize(finalState)}
                                        </script>
                                        ${jsScriptTagsFromAssets(assets, 'client', ' defer crossorigin')}
                                    </body>
                                </html>`;

                            return h.response(html).code(200);
                        }
                    }
                } catch (err) {
                    console.log(err);
                }
            },
        },
    });

    await server.start();
    console.log("Server run on %s", server.info.uri);
};

export default ser;