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)
|
||||
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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
&-search {
|
||||
display: flex;
|
||||
|
||||
&-filterButton {
|
||||
&-filterButton.ant-btn {
|
||||
display: none;
|
||||
margin-left: 0.5rem;
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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(),
|
||||
];
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 }} />
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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));
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue