1

我有一个使用 CRA、ConnectedRouter、Redux 和 Saga 制作的 React.JS 应用程序的 SSR 服务器。我正在尝试使用以下代码在 AWS Lambda 下托管此服务器:

const serverlessExpress = require('@vendia/serverless-express');
const app = require('./index');
const binaryMimeTypes = [
  'application/javascript',
  ...
  'text/xml'
];
exports.handler = serverlessExpress({
  app,
  binaryMimeTypes
}).handler;

此包装器提取以下设置代码:

const md5File = require('md5-file');
const fs = require('fs');
const path = require('path');

// CSS styles will be imported on load and that complicates matters... ignore those bad boys!
const ignoreStyles = require('ignore-styles');
const register = ignoreStyles.default;

// We also want to ignore all image requests
// When running locally these will load from a standard import
// When running on the server, we want to load via their hashed version in the build folder
const extensions = ['.gif', '.jpeg', '.jpg', '.png', '.svg'];

// Override the default style ignorer, also modifying all image requests
register(ignoreStyles.DEFAULT_EXTENSIONS, (mod, filename) => {
  if (!extensions.find(f => filename.endsWith(f))) {
    // If we find a style
    return ignoreStyles.noOp();
  }
  // for images that less than 10k, CRA will turn it into Base64 string, but here we have to do it again
  const stats = fs.statSync(filename);
  const fileSizeInBytes = stats.size / 1024;
  if (fileSizeInBytes <= 10) {
    mod.exports = `data:image/${mod.filename
      .split('.')
      .pop()};base64,${fs.readFileSync(mod.filename, {
      encoding: 'base64'
    })}`;
    return ignoreStyles.noOp();
  }

  // If we find an image
  const hash = md5File.sync(filename).slice(0, 8);
  const bn = path.basename(filename).replace(/(\.\w{3})$/, `.${hash}$1`);

  mod.exports = `/static/media/${bn}`;
});

// Set up babel to do its thing... env for the latest toys, react-app for CRA
// Notice three plugins: the first two allow us to use import rather than require, the third is for code splitting
// Polyfill is required for Babel 7, polyfill includes a custom regenerator runtime and core-js
require('@babel/polyfill');
require('@babel/register')({
  ignore: [/\/(build|node_modules)\//],
  presets: ['@babel/preset-env', '@babel/preset-react'],
  plugins: [
    '@babel/plugin-syntax-dynamic-import',
    '@babel/plugin-proposal-class-properties',
    'dynamic-import-node',
    'react-loadable/babel'
  ]
});

// Now that the nonsense is over... load up the server entry point
const app = require('./server');
module.exports = app;

然后我的 server.js 中有常规快递服务器

// Express requirements
import bodyParser from 'body-parser';
import compression from 'compression';
import express from 'express';
import morgan from 'morgan';
import path from 'path';
import forceDomain from 'forcedomain';
import Loadable from 'react-loadable';
import cookieParser from 'cookie-parser';

// Our loader - this basically acts as the entry point for each page load
import loader from './loader';

// Create our express app using the port optionally specified
const main = () => {
  const app = express();
  const PORT = process.env.PORT || 3000;
  // Compress, parse, log, and raid the cookie jar
  app.use(compression());
  app.use(bodyParser.json());
  app.use(bodyParser.urlencoded({ extended: false }));
  app.use(morgan('dev'));
  app.use(cookieParser());

  // Set up homepage, static assets, and capture everything else
  app.use(express.Router().get('/', loader));
  const favicon = require('serve-favicon');
  app.use(favicon(path.resolve(__dirname, '../build/icons/favicon.ico')));
  app.use(express.static(path.resolve(__dirname, '../build')));
  app.use(loader);

  // We tell React Loadable to load all required assets and start listening - ROCK AND ROLL!
  Loadable.preloadAll().then(() => {
    app.listen(PORT, console.log(`App listening on port ${PORT}!`));
  });

  // Handle the bugs somehow
  app.on('error', error => {
    if (error.syscall !== 'listen') {
      throw error;
    }

    const bind = typeof PORT === 'string' ? 'Pipe ' + PORT : 'Port ' + PORT;

    switch (error.code) {
      case 'EACCES':
        console.error(bind + ' requires elevated privileges');
        process.exit(1);
        break;
      case 'EADDRINUSE':
        console.error(bind + ' is already in use');
        process.exit(1);
        break;
      default:
        throw error;
    }
  });

  return app;
};

module.exports = main();

然后,我让装载机做:

// Express requirements
import path from 'path';
import fs from 'fs';

// React requirements
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 { Frontload, frontloadServerRender } from 'react-frontload';
import Loadable from 'react-loadable';

// Our store, entrypoint, and manifest
import createStore from '../src/configureStore';
import App from '../src/containers/app';
import manifest from '../build/asset-manifest.json';

// Some optional Redux functions related to user authentication
//import { setCurrentUser, logoutUser } from '../src/modules/auth';

// LOADER
export default (req, res) => {
  /*
    A simple helper function to prepare the HTML markup. This loads:
      - Page title
      - SEO meta tags
      - Preloaded state (for Redux) depending on the current route
      - Code-split script tags depending on the current route
  */
  const injectHTML = (data, { html, title, meta, body, scripts, state }) => {
    data = data.replace('<html>', `<html ${html}>`);
    data = data.replace(/<title>.*?<\/title>/g, title);
    data = data.replace('</head>', `${meta}</head>`);
    data = data.replace(
      '<div id="root"></div>',
      `<div id="root">${body}</div><script>window.__PRELOADED_STATE__ = ${state}</script>${scripts.join(
        ''
      )}`
    );

    return data;
  };

  // Load in our HTML file from our build
  fs.readFile(
    path.resolve(__dirname, '../build/index.html'),
    'utf8',
    (err, htmlData) => {
      ...

整个过程,包括转译,以及启动 express 服务器所需的时间,都需要相当长的时间。对于温暖的 lambda,我的延迟可能在 100 毫秒之间,一直到大约 3 秒。

是否有一些可以应用到我的代码中的容易实现的改进?

4

0 回答 0