Merge pull request #136 from grant-project/loadable-fixes

SSR & Build Fixes
This commit is contained in:
Daniel Ternyak 2019-01-31 22:05:30 -06:00 committed by GitHub
commit d0b1ccc08c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 57 additions and 91 deletions

View File

@ -1,7 +1,7 @@
# Disable typescript checking for dev building (reduce build time & resource usage)
NO_DEV_TS_CHECK=true
NODE_ENV=development
# NODE_ENV=development
# Set the public host url (no trailing slash)
PUBLIC_HOST_URL=https://grants.zfnd.org
@ -18,3 +18,6 @@ EXPLORER_URL="https://testnet.zcha.in/"
# Amount for staking a proposal in ZEC
PROPOSAL_STAKING_AMOUNT=0.025
# Normally production runs with SSL, this disables that
DISABLE_SSL=true

View File

@ -13,6 +13,7 @@ import AuthRoute from 'components/AuthRoute';
import Template, { TemplateProps } from 'components/Template';
// wrap components in loadable...import & they will be split
// Make sure you specify chunkname! Must replace slashes with dashes.
const opts = { fallback: <Loader size="large" /> };
const Home = loadable(() => import('pages/index'), opts);
const Create = loadable(() => import('pages/create'), opts);

View File

@ -22,7 +22,7 @@
&-search {
display: flex;
&-filterButton {
&-filterButton.ant-btn {
display: none;
margin-left: 0.5rem;

View File

@ -4,12 +4,6 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const isDev = process.env.NODE_ENV === 'development';
const babelPresets = [
'@babel/react',
// '@babel/typescript', (using ts-loader)
['@babel/env', { useBuiltIns: 'entry', modules: false }],
];
const lessLoader = {
loader: 'less-loader',
options: { javascriptEnabled: true },
@ -22,7 +16,6 @@ const tsBabelLoaderClient = {
loader: 'babel-loader',
options: {
plugins: [
'dynamic-import-webpack', // for client
'@loadable/babel-plugin',
'react-hot-loader/babel',
'@babel/plugin-proposal-object-rest-spread',
@ -46,7 +39,6 @@ const tsBabelLoaderServer = {
loader: 'babel-loader',
options: {
plugins: [
'dynamic-import-node', // for server
'@loadable/babel-plugin',
'@babel/plugin-proposal-object-rest-spread',
'@babel/plugin-proposal-class-properties',

View File

@ -1,7 +1,6 @@
const webpack = require('webpack');
const path = require('path');
const ManifestPlugin = require('webpack-manifest-plugin');
const { StatsWriterPlugin } = require('webpack-stats-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const ModuleDependencyWarning = require('./module-dependency-warning');
const WebappWebpackPlugin = require('webapp-webpack-plugin');
@ -55,27 +54,6 @@ const client = [
},
},
]),
// this allows the server access to the dependency graph
// so it can find which js/css to add to initial page
new StatsWriterPlugin({
fileName: 'stats.json',
fields: null,
transform(data) {
const trans = {};
trans.publicPath = data.publicPath;
trans.modules = data.modules.map(m => ({
id: m.id,
chunks: m.chunks,
reasons: m.reasons,
}));
trans.chunks = data.chunks.map(c => ({
id: c.id,
files: c.files,
origins: c.origins,
}));
return JSON.stringify(trans, null, 2);
},
}),
new LoadablePlugin(),
];

View File

@ -32,7 +32,6 @@
"@babel/core": "^7.0.1",
"@babel/plugin-proposal-class-properties": "^7.0.0",
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
"@babel/plugin-transform-modules-commonjs": "^7.0.0",
"@babel/polyfill": "^7.0.0",
"@babel/preset-env": "^7.0.0",
@ -74,8 +73,6 @@
"axios": "^0.18.0",
"babel-core": "^7.0.0-bridge.0",
"babel-loader": "^8.0.2",
"babel-plugin-dynamic-import-node": "^2.1.0",
"babel-plugin-dynamic-import-webpack": "^1.0.2",
"babel-plugin-import": "^1.8.0",
"babel-plugin-module-resolver": "^3.1.1",
"bn.js": "4.11.8",

View File

@ -59,12 +59,11 @@ const HTML: React.SFC<Props> = ({
{head.link.toComponent()}
{head.script.toComponent()}
{extractor.getStyleElements()}
{css.map(href => {
return <link key={href} rel="stylesheet" href={href} />;
return <link key={href} type="text/css" rel="stylesheet" href={href} />;
})}
{extractor.getStyleElements()}
{extractor.getScriptElements()}
<script
dangerouslySetInnerHTML={{
__html: `window.__PRELOADED_STATE__ = ${state}`,
@ -75,6 +74,7 @@ const HTML: React.SFC<Props> = ({
__html: `window.__PRELOADED_I18N__ = ${i18n}`,
}}
/>
{extractor.getScriptElements()}
</head>
<body>
<div id="app" dangerouslySetInnerHTML={{ __html: children }} />

View File

@ -28,8 +28,8 @@ Sentry.init({
const app = express();
// ssl
if (!isDev) {
console.log('Enabling HTTPS redirect.');
if (!isDev && !process.env.DISABLE_SSL) {
log.warn('PRODUCTION mode, enforcing HTTPS redirect');
app.use(enforce.HTTPS({ trustProtoHeader: true }));
}
@ -51,7 +51,7 @@ if (isDev) {
res.send('');
});
} else {
log.warn('PRODUCTION mode, serving static assets from node server.');
log.warn('PRODUCTION mode, serving static assets from node server');
app.use(
paths.publicPath,
express.static(path.join(paths.clientBuild, paths.publicPath)),

View File

@ -1,13 +1,11 @@
import path from 'path';
import React from 'react';
import { Request, Response } from 'express';
import { Request, Response, NextFunction } from 'express';
import { renderToString } from 'react-dom/server';
import { ChunkExtractor } from '@loadable/server';
import { StaticRouter as Router } from 'react-router-dom';
import { Provider } from 'react-redux';
import { I18nextProvider } from 'react-i18next';
import log from './log';
import { configureStore } from '../client/store/configure';
import Html from './components/HTML';
import Routes from '../client/Routes';
@ -19,7 +17,7 @@ import i18n from './i18n';
import * as paths from '../config/paths';
import { storeActionsForPath } from './ssrAsync';
const serverRenderer = () => async (req: Request, res: Response) => {
const serverRenderer = async (req: Request, res: Response) => {
const { store } = configureStore();
await storeActionsForPath(req.url, store);
@ -42,33 +40,15 @@ const serverRenderer = () => async (req: Request, res: Response) => {
let extractor;
// 1. loadable state will render dynamic imports
try {
const statsFile = path.join(
paths.clientBuild,
paths.publicPath,
'loadable-stats.json',
);
extractor = new ChunkExtractor({ statsFile, entrypoints: ['bundle'] });
} catch (e) {
const disp = `Error getting loadable state for SSR`;
e.message = disp + ': ' + e.message;
log.error(e);
return res.status(500).send(disp + ' (more info in server logs)');
}
const statsFile = path.join(paths.clientBuild, paths.publicPath, 'loadable-stats.json');
extractor = new ChunkExtractor({ statsFile, entrypoints: ['bundle'] });
// 2. render and collect state
const content = renderToString(reactApp);
const content = renderToString(extractor.collectChunks(reactApp));
const state = JSON.stringify(store.getState());
// ! ensure manifest.json is available
try {
res.locals.getManifest();
} catch (e) {
const disp =
'ERROR: Could not load client manifest.json, there was probably a client build error.';
log.error(disp);
return res.status(500).send(disp);
}
res.locals.getManifest();
const cssFiles = ['bundle.css', 'vendor.css']
.map(f => res.locals.assetPath(f))
@ -83,22 +63,27 @@ const serverRenderer = () => async (req: Request, res: Response) => {
.map(m => ({ ...m, content: res.locals.assetPath(m.content) }))
.filter(m => !!m.content);
return res.send(
'<!doctype html>' +
renderToString(
<Html
css={cssFiles}
scripts={jsFiles}
linkTags={mappedLinkTags}
metaTags={mappedMetaTags}
state={state}
i18n={i18nClient}
extractor={extractor}
>
{content}
</Html>,
),
const html = renderToString(
<Html
css={cssFiles}
scripts={jsFiles}
linkTags={mappedLinkTags}
metaTags={mappedMetaTags}
state={state}
i18n={i18nClient}
extractor={extractor}
>
{content}
</Html>,
);
return res.send('<!doctype html>' + html);
};
export default serverRenderer;
// Wrap in function to handle async errors
export default function() {
return (req: Request, res: Response, next: NextFunction) => {
serverRenderer(req, res)
.then(() => next())
.catch(err => next(err));
};
}

View File

@ -1,6 +1,7 @@
import { Store } from 'redux';
import { fetchUser } from 'modules/users/actions';
import { fetchProposals, fetchProposal } from 'modules/proposals/actions';
import { fetchRfps, fetchRfp } from 'modules/rfps/actions';
import { extractIdFromSlug } from 'utils/api';
const pathActions = [
@ -29,6 +30,22 @@ const pathActions = [
}
},
},
{
matcher: /^\/requests$/,
action: (_: RegExpMatchArray, store: Store) => {
return store.dispatch<any>(fetchRfps());
},
},
{
matcher: /^\/requests\/(.+)$/,
action: (match: RegExpMatchArray, store: Store) => {
const rfpId = extractIdFromSlug(match[1]);
if (rfpId) {
// return null for errors (404 most likely)
return store.dispatch<any>(fetchRfp(rfpId)).catch(() => null);
}
},
},
];
export function storeActionsForPath(path: string, store: Store) {

View File

@ -3220,20 +3220,13 @@ babel-messages@^6.23.0:
dependencies:
babel-runtime "^6.22.0"
babel-plugin-dynamic-import-node@^2.0.0, babel-plugin-dynamic-import-node@^2.1.0:
babel-plugin-dynamic-import-node@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.1.0.tgz#ce874a6a1b97091ae0f56fedef0237c0951ee553"
dependencies:
babel-plugin-syntax-dynamic-import "^6.18.0"
object.assign "^4.1.0"
babel-plugin-dynamic-import-webpack@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-webpack/-/babel-plugin-dynamic-import-webpack-1.0.2.tgz#cb83435833e073f1600c0188a95edacfdc07c256"
dependencies:
babel-plugin-syntax-dynamic-import "^6.18.0"
babel-template "^6.26.0"
babel-plugin-import@^1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/babel-plugin-import/-/babel-plugin-import-1.8.0.tgz#260deddd78f6fea0d110e1d106ba72a518d3c88c"