ssr.js 4.29 KB
const fs = require('fs');
const path = require('path');
const rp = require('request-promise');
const LRU = require('lru-cache');
const _ = require('lodash');
const pkg = require('../../package.json');
const logger = global.yoho.logger;
const {createBundleRenderer} = require('vue-server-renderer');

const routes = [
    {
        route: /product\/\d+/,
        cache: true,
        disable: true
    },
    {
        route: '/channel',
        cache: true
    },
    {
        route: '/channel/search',
        cache: true
    },
    {
        route: '/channel/men',
        cache: true
    },
    {
        route: '/channel/women',
        cache: true
    },
    {
        route: '/about',
        cache: true
    },
];

const isDev = process.env.NODE_ENV === 'development' || !process.env.NODE_ENV;
let renderer;
let template = fs.readFileSync(path.join(__dirname, '../../src/index.html'), 'utf-8');

const microCache = LRU({ // eslint-disable-line
    max: 10000,
    maxAge: 2000
});


const getContext = (req) => {
    return {
        url: req.url,
        title: 'BLK!',
        user: req.user,
        env: {
            isApp: req.yoho.isApp,
            isiOS: req.yoho.isiOS,
            isAndroid: req.yoho.isAndroid,
            isYohoBuy: req.yoho.isYohoBuy,
            channel: req.yoho.channel
        }
    };
};

const render = (options) => {
    return (req, res, next) => {
        if (options.cache) {
            const html = microCache.get(req.url);

            if (html) {
                console.log('cache', req.url);
                return res.send(html);
            }
        }
        let context = getContext(req);

        renderer.renderToString(context, (err, html) => {
            if (err) {
                // TODO 处理错误类型
                return next(err);
            }
            if (options.cache) {
                microCache.set(req.url, html);
            }
            return res.send(html);
        });
    };
};
const devRender = () => {
    return (req, res, next) => {
        let context = getContext(req);

        process.send({action: 'ssr_request', context});
        let event = msg => {
            process.removeListener('message', event);
            if (msg.action === 'ssr_request') {
                if (msg.err) {
                    try {
                        const err = JSON.parse(msg.err);

                        if (err.code === 404) {
                            return next();
                        }
                        return next(err);
                    } catch (e) {
                        return next({
                            code: 500,
                            message: msg.err
                        });
                    }
                }
                return res.end(msg.html);
            }
        };

        process.on('message', event);
    };
};

const loadBundle = async (errorCount = 0) => {
    if (!isDev) {
        if (errorCount > 5) {
            throw {
                code: 9999,
                message: 'ssr bundle download faild 5!'
            };
        }
        await Promise.all([
            rp(`http://cdn.yoho.cn/yohoblk-wap/bundle/yoho-ssr-server-${pkg.version}.json`, {json: true}),
            rp(`http://cdn.yoho.cn/yohoblk-wap/bundle/yoho-ssr-client-${pkg.version}.json`, {json: true}),
        ]).then(results => {
            logger.warn('ssr file is loaded');
            renderer = createBundleRenderer(results[0], {
                runInNewContext: false,
                template,
                clientManifest: results[1]
            });
        }).catch(() => {
            return loadBundle(errorCount + 1);
        });

        // const serverBundle = require(`../../public/dist/yohoblk-wap/bundle/yoho-ssr-server-${pkg.version}.json`);
        // const clientManifest = require(`../../public/dist/yohoblk-wap/bundle/yoho-ssr-client-${pkg.version}.json`);

        // renderer = createBundleRenderer(serverBundle, {
        //     runInNewContext: false,
        //     template,
        //     clientManifest
        // });
    }
};

const ssrRender = options => isDev ? devRender(options) : render(options);

module.exports = async (app) => {
    await loadBundle();
    _.each(routes, r => {
        if (!r.disable) {
            app.get(r.route, ssrRender({
                cache: r.cache
            }));
        }
    });
};