airbnb / hypernova

A service for server-side rendering your JavaScript views
MIT License
5.82k stars 207 forks source link

add express response.locals object to the job context object #112

Open magicmark opened 6 years ago

magicmark commented 6 years ago

Addresses https://github.com/airbnb/hypernova/issues/111

Motivation: To be able to pass things from inside getComponent to the rest of the express application (for custom middlewares)

(poked around in test/BatchManager-test.js, but can't see a place we're already asserting things about what is on the context object that we're passing to getComponent)

Thanks!

@goatslacker

magicmark commented 6 years ago

rebased.

@schleyfox @goatslacker anything I can do to help nudge this along to be merged? Thanks!

magicmark commented 6 years ago

@schleyfox @goatslacker Rebased again. ping :)

goatslacker commented 6 years ago

Motivation: To be able to pass things from inside getComponent to the rest of the express application (for custom middlewares)

What's an example of this? Maybe we can use this to create a test case for the feature.

magicmark commented 6 years ago

@goatslacker Thanks for the feedback!

What's an example of this? Maybe we can use this to create a test case for the feature.

We have a few logging middlewares that run at the end of the request. As part of a profiling thing we're doing, we want to include in the log - did the request hit a cold bundle cache or not?

Erring on the side of being overly-verbose, I'll share our sample usage:

So right now, using my forked version, a simplified version of our code looks something like

// (CRS = "component rendering service")

// A home for all the stuff that CRS adds to the response object per component token
type CRSComponentContext = {
    // Did the component request hit a cold cache?
    wasBundleInCache?: boolean,
};

export default async function getComponent(
    requestString: string,
    context: Object,
) {
    context.resLocals.crs = context.resLocals.crs || {};
    const componentContexts = (context.resLocals.crs: { [token: string]: CRSComponentContext });
    componentContexts[context.token] = {};
    const componentContext = componentContexts[context.token];

    // Decode requestString to get bundle/component info
    const componentRequest = getComponentInfo(requestString);

    // Fetch bundle from memory / disk / s3
    const [bundle, wasBundleInCache] = await getBundle(componentRequest.service, componentRequest.sha);

    componentContext.wasBundleInCache = wasBundleInCache;

    const component = bundle.default[componentRequest.name];

    return component;
}

This allows us in a middleware later on to access res.locals.crs[key]; and get arbitrary information about that specific component request.

(For example, somewhere else, in a logging middleware, we do something like)

// (uses https://www.npmjs.com/package/tweenz)
export default function AccessLog () {
    return async (requestDetails, req, res) => {
        // wait for request to complete
        await requestDetails;

        const componentRequests = Object.keys(hypernovaRequest).map(key => {
            const componentContext = res.locals.crs[key];
            return {
                // we'd also actually log some other stuff here too like timing, status etc
                ...componentContext,
            }
        });

        logSomewhere(componentRequests);
    };
};