diff --git a/package.json b/package.json index 4c6bd31cac..6b87db247a 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "pree2e": "yarn run config:prod", "pree2e:ci": "yarn run config:prod", "start": "yarn run start:prod", - "serve": "ng serve", + "serve": "ts-node --project ./tsconfig.ts-node.json scripts/serve.ts", "start:dev": "npm-run-all --parallel config:dev:watch serve", "start:prod": "yarn run build:prod && yarn run serve:ssr", "build": "ng build", diff --git a/scripts/serve.ts b/scripts/serve.ts new file mode 100644 index 0000000000..ff3554804e --- /dev/null +++ b/scripts/serve.ts @@ -0,0 +1,8 @@ +import { environment } from '../src/environments/environment'; + +import * as child from 'child_process'; + +child.spawn( + `ng serve --host ${environment.ui.host} --port ${environment.ui.port} --servePath ${environment.ui.nameSpace} --ssl ${environment.ui.ssl}`, + { stdio:'inherit', shell: true } +); diff --git a/server.ts b/server.ts index 31cefe4ec5..131b6bb4ae 100644 --- a/server.ts +++ b/server.ts @@ -1,75 +1,157 @@ -/** - * *** NOTE ON IMPORTING FROM ANGULAR AND NGUNIVERSAL IN THIS FILE *** - * - * If your application uses third-party dependencies, you'll need to - * either use Webpack or the Angular CLI's `bundleDependencies` feature - * in order to adequately package them for use on the server without a - * node_modules directory. - * - * However, due to the nature of the CLI's `bundleDependencies`, importing - * Angular in this file will create a different instance of Angular than - * the version in the compiled application code. This leads to unavoidable - * conflicts. Therefore, please do not explicitly import from @angular or - * @nguniversal in this file. You can export any needed resources - * from your application's main.server.ts file, as seen below with the - * import for `ngExpressEngine`. - */ - import 'zone.js/dist/zone-node'; import 'reflect-metadata'; +import 'rxjs'; +import * as fs from 'fs'; +import * as pem from 'pem'; +import * as https from 'https'; +import * as morgan from 'morgan'; import * as express from 'express'; -import { join } from 'path'; -import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens'; import * as bodyParser from 'body-parser'; +import * as compression from 'compression'; import * as cookieParser from 'cookie-parser'; + +import { enableProdMode, NgModuleFactory, Type } from '@angular/core'; + +import { ngExpressEngine } from '@nguniversal/express-engine'; + +import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens'; import { environment } from './src/environments/environment'; -// Express server -const app = express(); +export function startServer(bootstrap: Type<{}> | NgModuleFactory<{}>) { + const app = express(); -const PORT = environment.ui.port || 4000; -const DIST_FOLDER = join(process.cwd(), 'dist/browser'); + if (environment.production) { + enableProdMode(); + app.use(compression()); + } -// * NOTE :: leave this as require() since this file is built Dynamically from webpack -const { ServerAppModuleNgFactory, LAZY_MODULE_MAP, ngExpressEngine, provideModuleMap } = require('./dist/server/main'); + app.use(morgan('dev')); -app.use(cookieParser()); -app.use(bodyParser.json()); + app.use(cookieParser()); + app.use(bodyParser.json()); -app.engine('html', (_, options, callback) => - ngExpressEngine({ - bootstrap: ServerAppModuleNgFactory, - providers: [ - { - provide: REQUEST, - useValue: (options as any).req, - }, - { - provide: RESPONSE, - useValue: (options as any).req.res, - }, - provideModuleMap(LAZY_MODULE_MAP) - ], - })(_, options, callback) -); + app.engine('html', (_, options, callback) => + ngExpressEngine({ + bootstrap: bootstrap, + providers: [ + { + provide: REQUEST, + useValue: (options as any).req, + }, + { + provide: RESPONSE, + useValue: (options as any).req.res, + }, + ], + })(_, (options as any), callback) + ); -app.set('view engine', 'html'); -app.set('views', DIST_FOLDER); + app.set('view engine', 'ejs'); + app.set('view engine', 'html'); + app.set('views', 'src'); -// Example Express Rest API endpoints -// app.get('/api/**', (req, res) => { }); -// Serve static files from /browser -app.get('*.*', express.static(DIST_FOLDER, { - maxAge: '1y' -})); + function cacheControl(req, res, next) { + // instruct browser to revalidate + res.header('Cache-Control', environment.cache.control || 'max-age=60'); + next(); + } -// All regular routes use the Universal engine -app.get('*', (req, res) => { - res.render('index', { req }); -}); + app.use('/', cacheControl, express.static('dist', { index: false })); -// Start up the Node server -app.listen(PORT, () => { - console.log(`Node Express server listening on http://localhost:${PORT}`); -}); +// TODO: either remove or update mock backend +// app.get('/data.json', serverApi); +// app.use('/api', createMockApi()); + + function ngApp(req, res) { + const dspace = { + originalRequest: { + headers: req.headers, + body: req.body, + method: req.method, + params: req.params, + reportProgress: req.reportProgress, + withCredentials: req.withCredentials, + responseType: req.responseType, + urlWithParams: req.urlWithParams + } + }; + + function onHandleError(parentZoneDelegate, currentZone, targetZone, error) { + if (!res._headerSent) { + console.warn('Error in SSR, serving for direct CSR. Error details : ', error); + res.sendFile('index.csr.html', { root: './src' }); + } + } + + if (environment.universal.preboot) { + Zone.current.fork({ name: 'CSR fallback', onHandleError }).run(() => { + res.render('../dist/index.html', { + req, + res, + preboot: environment.universal.preboot, + async: environment.universal.async, + time: environment.universal.time, + baseUrl: environment.ui.nameSpace, + originUrl: environment.ui.baseUrl, + requestUrl: req.originalUrl + }); + }); + } else { + console.log('Universal off, serving for direct CSR'); + res.render('index-csr.ejs', { + root: './src', + scripts: `` + }); + } + } + + function serverStarted() { + console.log(`[${new Date().toTimeString()}] Listening at ${environment.ui.baseUrl}`); + } + + function createHttpsServer(keys) { + https.createServer({ + key: keys.serviceKey, + cert: keys.certificate + }, app).listen(environment.ui.port, environment.ui.host, () => { + serverStarted(); + }); + } + + if (environment.ui.ssl) { + let serviceKey; + try { + serviceKey = fs.readFileSync('./config/ssl/key.pem'); + } catch (e) { + console.warn('Service key not found at ./config/ssl/key.pem'); + } + + let certificate; + try { + certificate = fs.readFileSync('./config/ssl/cert.pem'); + } catch (e) { + console.warn('Certificate not found at ./config/ssl/key.pem'); + } + + if (serviceKey && certificate) { + createHttpsServer({ + serviceKey: serviceKey, + certificate: certificate + }); + } else { + + process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; + + pem.createCertificate({ + days: 1, + selfSigned: true + }, (error, keys) => { + createHttpsServer(keys); + }); + } + } else { + app.listen(environment.ui.port, environment.ui.host, () => { + serverStarted(); + }); + }} diff --git a/src/routes.ts b/src/routes.ts deleted file mode 100644 index f3e963b25a..0000000000 --- a/src/routes.ts +++ /dev/null @@ -1,15 +0,0 @@ -export const ROUTES: string[] = [ - 'home', - 'items/:id', - 'login', - 'logout', - 'collections/:id', - 'communities/:id', - 'login', - 'logout', - 'search', - 'submit', - 'workspaceitems/:id/edit', - 'workflowitems/:id/edit', - '**' -];