Skip to content

Commit

Permalink
feat: use pool of worker threads for rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
emuvente committed Jan 9, 2024
1 parent 952f822 commit 4d856dd
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 85 deletions.
68 changes: 31 additions & 37 deletions server/vue-middleware.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
const fs = require('fs');
const path = require('path');
const { Worker, SHARE_ENV } = require('worker_threads');
const { SHARE_ENV } = require('worker_threads');
const workerpool = require('workerpool');
const Bowser = require('bowser');
const cookie = require('cookie');
const log = require('./util/log');
const protectedRoutes = require('./util/protectedRoutes.js');
const tracer = require('./util/ddTrace');

Expand Down Expand Up @@ -40,6 +40,20 @@ module.exports = function createMiddleware({
// eslint-disable-next-line no-param-reassign
clientManifest.publicPath = config.app.publicPath || '/';

// Create a worker pool to render the app
const pool = workerpool.pool(path.resolve(__dirname, 'vue-worker.js'), {
workerType: 'thread',
workerThreadOpts: {
workerData: {
clientManifest,
serverBundle,
serverConfig: config.server,
template,
},
env: SHARE_ENV,
},
});

function middleware(req, res, next) {
const cookies = cookie.parse(req.headers.cookie || '');
const userAgent = req.get('user-agent');
Expand All @@ -65,42 +79,22 @@ module.exports = function createMiddleware({
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
}

// Create a worker thread to render the app
const worker = new Worker(path.resolve(__dirname, 'vue-worker.js'), {
workerData: {
clientManifest,
context,
serverBundle,
serverConfig: config.server,
template,
},
env: SHARE_ENV,
});

// Send the rendered html back to the client
worker.on('message', ({ error, html, setCookies }) => {
// set any cookies created during the app render
setCookies.forEach(setCookie => res.append('Set-Cookie', setCookie));

if (error) {
handleError(error, req, res, next);
} else {
// send the final rendered html
res.send(html);
}
});

// Handle any errors that occur in the worker
worker.on('error', err => {
handleError(err, req, res, next);
});
// render the app using the worker pool
pool.exec('render', [context])
.then(({ error, html, setCookies }) => {
// set any cookies created during the app render
setCookies.forEach(setCookie => res.append('Set-Cookie', setCookie));

// Handle the worker exiting
worker.on('exit', code => {
if (code !== 0) {
log.error(new Error(`Worker stopped with exit code ${code}`));
}
});
if (error) {
handleError(error, req, res, next);
} else {
// send the final rendered html
res.send(html);
}
})
.catch(err => {
handleError(err, req, res, next);
});
}

return tracer.wrap('vue-middleware', middleware);
Expand Down
107 changes: 59 additions & 48 deletions server/vue-worker.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const { parentPort, workerData } = require('worker_threads');
const { createBundleRenderer } = require('vue-server-renderer');
const { workerData } = require('worker_threads');
const workerpool = require('workerpool');
const initCache = require('./util/initCache');
const log = require('./util/log');
const vueSsrCache = require('./util/vueSsrCache');
Expand All @@ -8,14 +9,12 @@ const getSessionCookies = require('./util/getSessionCookies');

const {
clientManifest,
context,
serverBundle,
serverConfig,
template,
} = workerData;

const isProd = process.env.NODE_ENV === 'production';
const s = Date.now();
const cache = initCache(serverConfig);

// create a new renderer instance
Expand All @@ -29,52 +28,64 @@ const renderer = createBundleRenderer(serverBundle, {
shouldPrefetch: () => false,
});

// get graphql api possible types for the graphql client
const typesPromise = getGqlPossibleTypes(serverConfig.graphqlUri, cache)
.finally(() => {
if (!isProd) {
log.info(`fragment fetch: ${Date.now() - s}ms`);
}
});
async function render(context) {
const s = Date.now();

// fetch initial session cookies in case starting session with this request
const cookiePromise = getSessionCookies(serverConfig.sessionUri, context.cookies)
.finally(() => {
if (!isProd) {
log.info(`session fetch: ${Date.now() - s}ms`);
}
});

const setCookies = [];
// get graphql api possible types for the graphql client
const typesPromise = getGqlPossibleTypes(serverConfig.graphqlUri, cache)
.finally(() => {
if (!isProd) {
log.info(`fragment fetch: ${Date.now() - s}ms`);
}
});

Promise.all([typesPromise, cookiePromise])
.then(([types, cookieInfo]) => {
// add fetched types to rendering context
context.config.graphqlPossibleTypes = types;
// update cookies in the rendering context with any newly fetched session cookies
context.cookies = Object.assign(context.cookies, cookieInfo.cookies);
// forward any newly fetched 'Set-Cookie' headers
context.setCookies = [...cookieInfo.setCookies];
// render the app
return renderer.renderToString(context);
}).then(html => {
// collect any cookies created during the app render
setCookies.concat(context.setCookies);
// send the final rendered html
parentPort.postMessage({
html,
setCookies,
// fetch initial session cookies in case starting session with this request
const cookiePromise = getSessionCookies(serverConfig.sessionUri, context.cookies)
.finally(() => {
if (!isProd) {
log.info(`session fetch: ${Date.now() - s}ms`);
}
});
if (!isProd) {
log.info(`whole request: ${Date.now() - s}ms`);
}
}).catch(err => {
// collect any cookies created during the app render
setCookies.concat(context.setCookies);
// send the error
parentPort.postMessage({
error: err,
// only send cookies if there is a redirect url
setCookies: err.url ? setCookies : [],

const setCookies = [];

return Promise.all([typesPromise, cookiePromise])
.then(([types, cookieInfo]) => {
// add fetched types to rendering context
context.config.graphqlPossibleTypes = types;
// update cookies in the rendering context with any newly fetched session cookies
context.cookies = Object.assign(context.cookies, cookieInfo.cookies);
// forward any newly fetched 'Set-Cookie' headers
context.setCookies = [...cookieInfo.setCookies];
// render the app
return renderer.renderToString(context);
})
.then(html => {
// collect any cookies created during the app render
setCookies.concat(context.setCookies);
// send the final rendered html
return {
html,
setCookies,
};
})
.catch(err => {
// collect any cookies created during the app render
setCookies.concat(context.setCookies);
// send the error
return {
error: err,
// only send cookies if there is a redirect url
setCookies: err.url ? setCookies : [],
};
})
.finally(() => {
if (!isProd) {
log.info(`whole request: ${Date.now() - s}ms`);
}
});
});
}

workerpool.worker({
render,
});

0 comments on commit 4d856dd

Please sign in to comment.