diff --git a/src/response.js b/src/response.js index f8798ec9..730b2f3e 100644 --- a/src/response.js +++ b/src/response.js @@ -679,7 +679,8 @@ module.exports = class Response extends Writable { this.send(str); }); - this.app.render(view, options, done); + // use req.app like express does, so mounted sub-apps resolve views with their own settings + this.req.app.render(view, options, done); } cookie(name, value, options) { const opt = {...(options ?? {})}; // create a new ref because we change original object (https://github.com/dimdenGD/ultimate-express/issues/68) diff --git a/src/router.js b/src/router.js index 3846e0cb..9981add5 100644 --- a/src/router.js +++ b/src/router.js @@ -241,7 +241,9 @@ module.exports = class Router extends EventEmitter { [...chainPrefix, ...pathToMount, { ...route, callbacks: [], - keepMount: true + keepMount: true, + // mounted sub-apps become req.app during their dispatch, like express + mountApp: route.callbacks[0].constructor.name === 'Application' ? route.callbacks[0] : undefined }] ); } @@ -495,6 +497,7 @@ module.exports = class Router extends EventEmitter { return false; } // on optimized routes, there can be more routes, so we have to use unoptimized routing and skip until we find route we stopped at + req.app = this; // restore app in case the optimized path swapped it to a mounted sub-app return this._routeRequest(req, res, 0, this._routes, false, skipUntil); } let callbackindex = 0; @@ -507,6 +510,11 @@ module.exports = class Router extends EventEmitter { const strictRouting = this.get('strict routing'); if(route.use) { + if(route.mountApp) { + // optimized chain: normal dispatch swaps req.app when it enters a mounted Application, + // but the compiled mount route has no callback to do it, so swap it here + req.app = route.mountApp; + } req._stack.push(route.path); const fullMountpath = this.getFullMountpath(req); req._opPath = fullMountpath !== EMPTY_REGEX ? req._originalPath.replace(fullMountpath, '') : req._originalPath; diff --git a/tests/parts/subapp/index.ejs b/tests/parts/subapp/index.ejs new file mode 100644 index 00000000..8fdbc0b3 --- /dev/null +++ b/tests/parts/subapp/index.ejs @@ -0,0 +1,2 @@ +

subapp <%= message %>

+<%= asdf %> diff --git a/tests/tests/res/res-render-subapp.js b/tests/tests/res/res-render-subapp.js new file mode 100644 index 00000000..ad334d66 --- /dev/null +++ b/tests/tests/res/res-render-subapp.js @@ -0,0 +1,50 @@ +// res.render must use the views and engine of the sub-app handling the request + +const express = require("express"); + +const app = express(); +app.set('view engine', 'ejs'); +app.set('views', 'tests/parts'); +app.set('env', 'production'); + +const subApp = express(); +subApp.set('view engine', 'ejs'); +subApp.set('views', 'tests/parts/subapp'); +subApp.set('env', 'production'); + +subApp.get('/page', (req, res) => { + res.locals.asdf = 'locals test'; + res.render('index.ejs', { message: 'from subapp' }); +}); + +subApp.get('/fallthrough', (req, res, next) => { + next(); +}); + +app.use('/sub', subApp); + +app.use((req, res) => { + res.locals.asdf = 'locals test'; + res.render('index.ejs', { title: 'Outer', message: 'from outer' }); +}); + +app.use((err, req, res, next) => { + console.log(err.message.split('\n')[0]); + res.status(500).send('whoops!'); +}); + +app.listen(13333, async () => { + console.log('Server is running on port 13333'); + + // before the router optimization kicks in + console.log(await fetch('http://localhost:13333/sub/page').then(res => res.text())); + console.log(await fetch('http://localhost:13333/sub/fallthrough').then(res => res.text())); + + await new Promise(resolve => setTimeout(resolve, 1000)); + + // after the router optimization kicks in + console.log(await fetch('http://localhost:13333/sub/page').then(res => res.text())); + console.log(await fetch('http://localhost:13333/sub/fallthrough').then(res => res.text())); + + process.exit(0); +});