diff --git a/config/watcher.ts b/config/watcher.ts index 050fa298b..33facd227 100644 --- a/config/watcher.ts +++ b/config/watcher.ts @@ -110,6 +110,13 @@ const config: Config = { ignore: ["core/client/**/*"], executor: new LongRunningExecutor("npm run --silent start:development"), }, + runServerWithoutClientRoutes: { + paths: ["locales/**/*.ftl"], + ignore: ["core/client/**/*"], + executor: new LongRunningExecutor( + "DISABLE_CLIENT_ROUTES=true npm run --silent start:development" + ), + }, runServerLint: { paths: ["core/**/*.ts"], ignore: ["core/client/**/*"], @@ -140,7 +147,7 @@ const config: Config = { "runServerSyntaxCheck", ], client: [ - "runServer", + "runServerWithoutClientRoutes", "runServerLint", "runServerSyntaxCheck", "runWebpackDevServer", diff --git a/src/core/server/app/index.ts b/src/core/server/app/index.ts index 1d3f02a53..439f00dcc 100644 --- a/src/core/server/app/index.ts +++ b/src/core/server/app/index.ts @@ -36,6 +36,7 @@ export interface AppOptions { signingConfig: JWTSigningConfig; tenantCache: TenantCache; metrics: boolean; + disableClientRoutes: boolean; } /** @@ -58,7 +59,13 @@ export async function createApp(options: AppOptions): Promise { const passport = createPassport(options); // Mount the router. - parent.use("/", createRouter(options, { passport })); + parent.use( + "/", + createRouter(options, { + passport, + disableClientRoutes: options.disableClientRoutes, + }) + ); // Enable CORS headers for media assets, font's require them. parent.use("/assets/media", cors()); diff --git a/src/core/server/app/router/client.ts b/src/core/server/app/router/client.ts index b497fbcd2..a13aed66e 100644 --- a/src/core/server/app/router/client.ts +++ b/src/core/server/app/router/client.ts @@ -1,8 +1,15 @@ -import express from "express"; +import express, { Router } from "express"; import { minify } from "html-minifier"; +import path from "path"; import { cacheHeadersMiddleware } from "talk-server/app/middleware/cacheHeaders"; -import { Entrypoint } from "../helpers/entrypoints"; +import { cspTenantMiddleware } from "talk-server/app/middleware/csp/tenant"; +import { installedMiddleware } from "talk-server/app/middleware/installed"; +import { tenantMiddleware } from "talk-server/app/middleware/tenant"; +import logger from "talk-server/logger"; +import TenantCache from "talk-server/services/tenant/cache"; + +import Entrypoints, { Entrypoint } from "../helpers/entrypoints"; export interface ClientTargetHandlerOptions { /** @@ -28,7 +35,7 @@ export interface ClientTargetHandlerOptions { staticURI: string; } -export function createClientTargetRouter({ +function createClientTargetRouter({ staticURI, entrypoint, enableCustomCSS = false, @@ -63,3 +70,116 @@ export function createClientTargetRouter({ return router; } + +interface MountClientRouteOptions { + tenantCache: TenantCache; + staticURI: string; +} + +export function mountClientRoutes( + router: Router, + { staticURI, tenantCache }: MountClientRouteOptions +) { + // TODO: (wyattjoh) figure out a better way of referencing paths. + // Load the entrypoint manifest. + const manifest = path.join( + __dirname, + "..", + "..", + "..", + "..", + "..", + "dist", + "static", + "asset-manifest.json" + ); + const entrypoints = Entrypoints.fromFile(manifest); + if (!entrypoints) { + logger.error( + { manifest }, + "could not load the generated manifest, client routes will remain un-mounted" + ); + return; + } + // Tenant identification middleware. + router.use( + tenantMiddleware({ + cache: tenantCache, + passNoTenant: true, + }) + ); + + // Add CSP headers to the request, which only apply when serving HTML content. + router.use(cspTenantMiddleware); + + // Add the embed targets. + router.use( + "/embed/stream", + createClientTargetRouter({ + staticURI, + enableCustomCSS: true, + entrypoint: entrypoints.get("stream"), + }) + ); + router.use( + "/embed/auth", + createClientTargetRouter({ + staticURI, + cacheDuration: false, + entrypoint: entrypoints.get("auth"), + }) + ); + router.use( + "/embed/auth/callback", + createClientTargetRouter({ + staticURI, + cacheDuration: false, + entrypoint: entrypoints.get("authCallback"), + }) + ); + + // Add the standalone targets. + router.use( + "/account", + // If we aren't already installed, redirect the user to the install page. + installedMiddleware(), + createClientTargetRouter({ + staticURI, + cacheDuration: false, + entrypoint: entrypoints.get("account"), + }) + ); + // Add the standalone targets. + router.use( + "/admin", + // If we aren't already installed, redirect the user to the install page. + installedMiddleware(), + createClientTargetRouter({ + staticURI, + cacheDuration: false, + entrypoint: entrypoints.get("admin"), + }) + ); + router.use( + "/install", + // If we're already installed, redirect the user to the admin page. + installedMiddleware({ + redirectIfInstalled: true, + redirectURL: "/admin", + }), + createClientTargetRouter({ + staticURI, + cacheDuration: false, + entrypoint: entrypoints.get("install"), + }) + ); + + // Handle the root path. + router.get( + "/", + // Redirect the user to the install page if they are not, otherwise redirect + // them to the admin. + installedMiddleware(), + (req, res, next) => res.redirect("/admin") + ); +} diff --git a/src/core/server/app/router/index.ts b/src/core/server/app/router/index.ts index c128e8a55..50cf49c9a 100644 --- a/src/core/server/app/router/index.ts +++ b/src/core/server/app/router/index.ts @@ -1,20 +1,15 @@ import express, { Router } from "express"; -import path from "path"; import { register } from "prom-client"; import { AppOptions } from "talk-server/app"; import { noCacheMiddleware } from "talk-server/app/middleware/cacheHeaders"; -import { cspTenantMiddleware } from "talk-server/app/middleware/csp/tenant"; -import { installedMiddleware } from "talk-server/app/middleware/installed"; import playground from "talk-server/app/middleware/playground"; -import { tenantMiddleware } from "talk-server/app/middleware/tenant"; import { RouterOptions } from "talk-server/app/router/types"; import logger from "talk-server/logger"; -import Entrypoints from "../helpers/entrypoints"; import { basicAuth } from "../middleware/basicAuth"; import { createAPIRouter } from "./api"; -import { createClientTargetRouter } from "./client"; +import { mountClientRoutes } from "./client"; export function createRouter(app: AppOptions, options: RouterOptions) { // Create a router. @@ -28,109 +23,13 @@ export function createRouter(app: AppOptions, options: RouterOptions) { attachGraphiQL(router, app); } - // TODO: (wyattjoh) figure out a better way of referencing paths. - // Load the entrypoint manifest. - const manifest = path.join( - __dirname, - "..", - "..", - "..", - "..", - "..", - "dist", - "static", - "asset-manifest.json" - ); - const entrypoints = Entrypoints.fromFile(manifest); - if (entrypoints) { - // Tenant identification middleware. - router.use( - tenantMiddleware({ - cache: app.tenantCache, - passNoTenant: true, - }) - ); - - // Add CSP headers to the request, which only apply when serving HTML content. - router.use(cspTenantMiddleware); - - const staticURI = app.config.get("static_uri"); - - // Add the embed targets. - router.use( - "/embed/stream", - createClientTargetRouter({ - staticURI, - enableCustomCSS: true, - entrypoint: entrypoints.get("stream"), - }) - ); - router.use( - "/embed/auth", - createClientTargetRouter({ - staticURI, - cacheDuration: false, - entrypoint: entrypoints.get("auth"), - }) - ); - router.use( - "/embed/auth/callback", - createClientTargetRouter({ - staticURI, - cacheDuration: false, - entrypoint: entrypoints.get("authCallback"), - }) - ); - - // Add the standalone targets. - router.use( - "/account", - // If we aren't already installed, redirect the user to the install page. - installedMiddleware(), - createClientTargetRouter({ - staticURI, - cacheDuration: false, - entrypoint: entrypoints.get("account"), - }) - ); - // Add the standalone targets. - router.use( - "/admin", - // If we aren't already installed, redirect the user to the install page. - installedMiddleware(), - createClientTargetRouter({ - staticURI, - cacheDuration: false, - entrypoint: entrypoints.get("admin"), - }) - ); - router.use( - "/install", - // If we're already installed, redirect the user to the admin page. - installedMiddleware({ - redirectIfInstalled: true, - redirectURL: "/admin", - }), - createClientTargetRouter({ - staticURI, - cacheDuration: false, - entrypoint: entrypoints.get("install"), - }) - ); - - // Handle the root path. - router.get( - "/", - // Redirect the user to the install page if they are not, otherwise redirect - // them to the admin. - installedMiddleware(), - (req, res, next) => res.redirect("/admin") - ); + if (!options.disableClientRoutes) { + mountClientRoutes(router, { + staticURI: app.config.get("static_uri"), + tenantCache: app.tenantCache, + }); } else { - logger.warn( - { manifest }, - "could not load the generated manifest, client routes will remain un-mounted" - ); + logger.warn("client routes are disabled"); } if (app.metrics) { diff --git a/src/core/server/app/router/types.ts b/src/core/server/app/router/types.ts index 740f43b66..c6fc56d87 100644 --- a/src/core/server/app/router/types.ts +++ b/src/core/server/app/router/types.ts @@ -6,4 +6,9 @@ export interface RouterOptions { * and mount new authentication middleware. */ passport: passport.Authenticator; + + /** + * disableClientRoutes will not mount the routes to the client bundles. + */ + disableClientRoutes: boolean; } diff --git a/src/core/server/config.ts b/src/core/server/config.ts index 9e29bc6c3..84de8b9b4 100644 --- a/src/core/server/config.ts +++ b/src/core/server/config.ts @@ -181,6 +181,14 @@ const config = convict({ env: "DISABLE_MONGODB_AUTOINDEXING", arg: "disableMongoDBAutoindexing", }, + disable_client_routes: { + doc: + "Disables mounting of client routes for developing with Webpack Dev Server", + format: Boolean, + default: false, + env: "DISABLE_CLIENT_ROUTES", + arg: "disableClientRoutes", + }, }); export type Config = typeof config; diff --git a/src/core/server/index.ts b/src/core/server/index.ts index b1e3a7424..d1aede8e1 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -241,6 +241,10 @@ class Server { // Only enable the metrics server if concurrency is set to 1. const metrics = this.config.get("concurrency") === 1; + // Disables the client routes to serve bundles etc. Useful for devleoping with + // Webpack Dev Server. + const disableClientRoutes = this.config.get("disable_client_routes"); + // Create the Talk App, branching off from the parent app. const app: Express = await createApp({ parent, @@ -254,6 +258,7 @@ class Server { mailerQueue: this.tasks.mailer, scraperQueue: this.tasks.scraper, metrics, + disableClientRoutes, }); // Start the application and store the resulting http.Server. The server