Merge pull request #136 from grant-project/loadable-fixes
SSR & Build Fixes
This commit is contained in:
commit
d0b1ccc08c
|
@ -1,7 +1,7 @@
|
||||||
# Disable typescript checking for dev building (reduce build time & resource usage)
|
# Disable typescript checking for dev building (reduce build time & resource usage)
|
||||||
NO_DEV_TS_CHECK=true
|
NO_DEV_TS_CHECK=true
|
||||||
|
|
||||||
NODE_ENV=development
|
# NODE_ENV=development
|
||||||
|
|
||||||
# Set the public host url (no trailing slash)
|
# Set the public host url (no trailing slash)
|
||||||
PUBLIC_HOST_URL=https://grants.zfnd.org
|
PUBLIC_HOST_URL=https://grants.zfnd.org
|
||||||
|
@ -18,3 +18,6 @@ EXPLORER_URL="https://testnet.zcha.in/"
|
||||||
|
|
||||||
# Amount for staking a proposal in ZEC
|
# Amount for staking a proposal in ZEC
|
||||||
PROPOSAL_STAKING_AMOUNT=0.025
|
PROPOSAL_STAKING_AMOUNT=0.025
|
||||||
|
|
||||||
|
# Normally production runs with SSL, this disables that
|
||||||
|
DISABLE_SSL=true
|
||||||
|
|
|
@ -13,6 +13,7 @@ import AuthRoute from 'components/AuthRoute';
|
||||||
import Template, { TemplateProps } from 'components/Template';
|
import Template, { TemplateProps } from 'components/Template';
|
||||||
|
|
||||||
// wrap components in loadable...import & they will be split
|
// 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 opts = { fallback: <Loader size="large" /> };
|
||||||
const Home = loadable(() => import('pages/index'), opts);
|
const Home = loadable(() => import('pages/index'), opts);
|
||||||
const Create = loadable(() => import('pages/create'), opts);
|
const Create = loadable(() => import('pages/create'), opts);
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
&-search {
|
&-search {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
&-filterButton {
|
&-filterButton.ant-btn {
|
||||||
display: none;
|
display: none;
|
||||||
margin-left: 0.5rem;
|
margin-left: 0.5rem;
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,6 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
|
|
||||||
const isDev = process.env.NODE_ENV === 'development';
|
const isDev = process.env.NODE_ENV === 'development';
|
||||||
|
|
||||||
const babelPresets = [
|
|
||||||
'@babel/react',
|
|
||||||
// '@babel/typescript', (using ts-loader)
|
|
||||||
['@babel/env', { useBuiltIns: 'entry', modules: false }],
|
|
||||||
];
|
|
||||||
|
|
||||||
const lessLoader = {
|
const lessLoader = {
|
||||||
loader: 'less-loader',
|
loader: 'less-loader',
|
||||||
options: { javascriptEnabled: true },
|
options: { javascriptEnabled: true },
|
||||||
|
@ -22,7 +16,6 @@ const tsBabelLoaderClient = {
|
||||||
loader: 'babel-loader',
|
loader: 'babel-loader',
|
||||||
options: {
|
options: {
|
||||||
plugins: [
|
plugins: [
|
||||||
'dynamic-import-webpack', // for client
|
|
||||||
'@loadable/babel-plugin',
|
'@loadable/babel-plugin',
|
||||||
'react-hot-loader/babel',
|
'react-hot-loader/babel',
|
||||||
'@babel/plugin-proposal-object-rest-spread',
|
'@babel/plugin-proposal-object-rest-spread',
|
||||||
|
@ -46,7 +39,6 @@ const tsBabelLoaderServer = {
|
||||||
loader: 'babel-loader',
|
loader: 'babel-loader',
|
||||||
options: {
|
options: {
|
||||||
plugins: [
|
plugins: [
|
||||||
'dynamic-import-node', // for server
|
|
||||||
'@loadable/babel-plugin',
|
'@loadable/babel-plugin',
|
||||||
'@babel/plugin-proposal-object-rest-spread',
|
'@babel/plugin-proposal-object-rest-spread',
|
||||||
'@babel/plugin-proposal-class-properties',
|
'@babel/plugin-proposal-class-properties',
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const ManifestPlugin = require('webpack-manifest-plugin');
|
const ManifestPlugin = require('webpack-manifest-plugin');
|
||||||
const { StatsWriterPlugin } = require('webpack-stats-plugin');
|
|
||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
const ModuleDependencyWarning = require('./module-dependency-warning');
|
const ModuleDependencyWarning = require('./module-dependency-warning');
|
||||||
const WebappWebpackPlugin = require('webapp-webpack-plugin');
|
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(),
|
new LoadablePlugin(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,6 @@
|
||||||
"@babel/core": "^7.0.1",
|
"@babel/core": "^7.0.1",
|
||||||
"@babel/plugin-proposal-class-properties": "^7.0.0",
|
"@babel/plugin-proposal-class-properties": "^7.0.0",
|
||||||
"@babel/plugin-proposal-object-rest-spread": "^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/plugin-transform-modules-commonjs": "^7.0.0",
|
||||||
"@babel/polyfill": "^7.0.0",
|
"@babel/polyfill": "^7.0.0",
|
||||||
"@babel/preset-env": "^7.0.0",
|
"@babel/preset-env": "^7.0.0",
|
||||||
|
@ -74,8 +73,6 @@
|
||||||
"axios": "^0.18.0",
|
"axios": "^0.18.0",
|
||||||
"babel-core": "^7.0.0-bridge.0",
|
"babel-core": "^7.0.0-bridge.0",
|
||||||
"babel-loader": "^8.0.2",
|
"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-import": "^1.8.0",
|
||||||
"babel-plugin-module-resolver": "^3.1.1",
|
"babel-plugin-module-resolver": "^3.1.1",
|
||||||
"bn.js": "4.11.8",
|
"bn.js": "4.11.8",
|
||||||
|
|
|
@ -59,12 +59,11 @@ const HTML: React.SFC<Props> = ({
|
||||||
{head.link.toComponent()}
|
{head.link.toComponent()}
|
||||||
{head.script.toComponent()}
|
{head.script.toComponent()}
|
||||||
|
|
||||||
{extractor.getStyleElements()}
|
|
||||||
{css.map(href => {
|
{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
|
<script
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: `window.__PRELOADED_STATE__ = ${state}`,
|
__html: `window.__PRELOADED_STATE__ = ${state}`,
|
||||||
|
@ -75,6 +74,7 @@ const HTML: React.SFC<Props> = ({
|
||||||
__html: `window.__PRELOADED_I18N__ = ${i18n}`,
|
__html: `window.__PRELOADED_I18N__ = ${i18n}`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{extractor.getScriptElements()}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app" dangerouslySetInnerHTML={{ __html: children }} />
|
<div id="app" dangerouslySetInnerHTML={{ __html: children }} />
|
||||||
|
|
|
@ -28,8 +28,8 @@ Sentry.init({
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
// ssl
|
// ssl
|
||||||
if (!isDev) {
|
if (!isDev && !process.env.DISABLE_SSL) {
|
||||||
console.log('Enabling HTTPS redirect.');
|
log.warn('PRODUCTION mode, enforcing HTTPS redirect');
|
||||||
app.use(enforce.HTTPS({ trustProtoHeader: true }));
|
app.use(enforce.HTTPS({ trustProtoHeader: true }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ if (isDev) {
|
||||||
res.send('');
|
res.send('');
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
log.warn('PRODUCTION mode, serving static assets from node server.');
|
log.warn('PRODUCTION mode, serving static assets from node server');
|
||||||
app.use(
|
app.use(
|
||||||
paths.publicPath,
|
paths.publicPath,
|
||||||
express.static(path.join(paths.clientBuild, paths.publicPath)),
|
express.static(path.join(paths.clientBuild, paths.publicPath)),
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Request, Response } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { renderToString } from 'react-dom/server';
|
import { renderToString } from 'react-dom/server';
|
||||||
import { ChunkExtractor } from '@loadable/server';
|
import { ChunkExtractor } from '@loadable/server';
|
||||||
import { StaticRouter as Router } from 'react-router-dom';
|
import { StaticRouter as Router } from 'react-router-dom';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { I18nextProvider } from 'react-i18next';
|
import { I18nextProvider } from 'react-i18next';
|
||||||
|
|
||||||
import log from './log';
|
|
||||||
import { configureStore } from '../client/store/configure';
|
import { configureStore } from '../client/store/configure';
|
||||||
import Html from './components/HTML';
|
import Html from './components/HTML';
|
||||||
import Routes from '../client/Routes';
|
import Routes from '../client/Routes';
|
||||||
|
@ -19,7 +17,7 @@ import i18n from './i18n';
|
||||||
import * as paths from '../config/paths';
|
import * as paths from '../config/paths';
|
||||||
import { storeActionsForPath } from './ssrAsync';
|
import { storeActionsForPath } from './ssrAsync';
|
||||||
|
|
||||||
const serverRenderer = () => async (req: Request, res: Response) => {
|
const serverRenderer = async (req: Request, res: Response) => {
|
||||||
const { store } = configureStore();
|
const { store } = configureStore();
|
||||||
await storeActionsForPath(req.url, store);
|
await storeActionsForPath(req.url, store);
|
||||||
|
|
||||||
|
@ -42,33 +40,15 @@ const serverRenderer = () => async (req: Request, res: Response) => {
|
||||||
|
|
||||||
let extractor;
|
let extractor;
|
||||||
// 1. loadable state will render dynamic imports
|
// 1. loadable state will render dynamic imports
|
||||||
try {
|
const statsFile = path.join(paths.clientBuild, paths.publicPath, 'loadable-stats.json');
|
||||||
const statsFile = path.join(
|
extractor = new ChunkExtractor({ statsFile, entrypoints: ['bundle'] });
|
||||||
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)');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. render and collect state
|
// 2. render and collect state
|
||||||
const content = renderToString(reactApp);
|
const content = renderToString(extractor.collectChunks(reactApp));
|
||||||
const state = JSON.stringify(store.getState());
|
const state = JSON.stringify(store.getState());
|
||||||
|
|
||||||
// ! ensure manifest.json is available
|
// ! ensure manifest.json is available
|
||||||
try {
|
res.locals.getManifest();
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
const cssFiles = ['bundle.css', 'vendor.css']
|
const cssFiles = ['bundle.css', 'vendor.css']
|
||||||
.map(f => res.locals.assetPath(f))
|
.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) }))
|
.map(m => ({ ...m, content: res.locals.assetPath(m.content) }))
|
||||||
.filter(m => !!m.content);
|
.filter(m => !!m.content);
|
||||||
|
|
||||||
return res.send(
|
const html = renderToString(
|
||||||
'<!doctype html>' +
|
<Html
|
||||||
renderToString(
|
css={cssFiles}
|
||||||
<Html
|
scripts={jsFiles}
|
||||||
css={cssFiles}
|
linkTags={mappedLinkTags}
|
||||||
scripts={jsFiles}
|
metaTags={mappedMetaTags}
|
||||||
linkTags={mappedLinkTags}
|
state={state}
|
||||||
metaTags={mappedMetaTags}
|
i18n={i18nClient}
|
||||||
state={state}
|
extractor={extractor}
|
||||||
i18n={i18nClient}
|
>
|
||||||
extractor={extractor}
|
{content}
|
||||||
>
|
</Html>,
|
||||||
{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));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Store } from 'redux';
|
import { Store } from 'redux';
|
||||||
import { fetchUser } from 'modules/users/actions';
|
import { fetchUser } from 'modules/users/actions';
|
||||||
import { fetchProposals, fetchProposal } from 'modules/proposals/actions';
|
import { fetchProposals, fetchProposal } from 'modules/proposals/actions';
|
||||||
|
import { fetchRfps, fetchRfp } from 'modules/rfps/actions';
|
||||||
import { extractIdFromSlug } from 'utils/api';
|
import { extractIdFromSlug } from 'utils/api';
|
||||||
|
|
||||||
const pathActions = [
|
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) {
|
export function storeActionsForPath(path: string, store: Store) {
|
||||||
|
|
|
@ -3220,20 +3220,13 @@ babel-messages@^6.23.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
babel-runtime "^6.22.0"
|
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"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.1.0.tgz#ce874a6a1b97091ae0f56fedef0237c0951ee553"
|
resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.1.0.tgz#ce874a6a1b97091ae0f56fedef0237c0951ee553"
|
||||||
dependencies:
|
dependencies:
|
||||||
babel-plugin-syntax-dynamic-import "^6.18.0"
|
babel-plugin-syntax-dynamic-import "^6.18.0"
|
||||||
object.assign "^4.1.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:
|
babel-plugin-import@^1.8.0:
|
||||||
version "1.8.0"
|
version "1.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/babel-plugin-import/-/babel-plugin-import-1.8.0.tgz#260deddd78f6fea0d110e1d106ba72a518d3c88c"
|
resolved "https://registry.yarnpkg.com/babel-plugin-import/-/babel-plugin-import-1.8.0.tgz#260deddd78f6fea0d110e1d106ba72a518d3c88c"
|
||||||
|
|
Loading…
Reference in New Issue