|
|
const fs = require('fs');
|
|
|
const path = require('path');
|
|
|
const url = require('url');
|
|
|
const sourceMap = require('source-map');
|
|
|
const _ = require('lodash');
|
|
|
const os = require('os');
|
|
|
const md5 = require('yoho-md5');
|
|
|
const pkg = require('../../package.json');
|
|
|
const routes = require('../../config/ssr-routes');
|
|
|
const redis = require('../../utils/redis');
|
|
|
const routeEncode = require('../../utils/route-encode');
|
|
|
const {createBundleRenderer} = require('vue-server-renderer');
|
|
|
const logger = global.yoho.logger;
|
|
|
const config = global.yoho.config;
|
|
|
|
|
|
const REG_STACK = /at ([^:]+):(\d+):(\d+)/;
|
|
|
|
|
|
const isDev = process.env.NODE_ENV === 'development' || !process.env.NODE_ENV;
|
|
|
|
|
|
let renderer;
|
...
|
...
|
@@ -41,11 +40,26 @@ const getContext = (req) => { |
|
|
isiOS: req.yoho.isiOS,
|
|
|
isAndroid: req.yoho.isAndroid,
|
|
|
isYohoApp: req.yoho.isYohoApp,
|
|
|
clientIp: req.yoho.clientIp
|
|
|
}
|
|
|
clientIp: req.yoho.clientIp,
|
|
|
},
|
|
|
ua: req.get('user-agent'),
|
|
|
hostname: os.hostname(),
|
|
|
route: `[${req.method}]${_.get(req, 'route.path', '')}`, // 请求路由
|
|
|
udid: _.get(req, 'cookies.udid', 'yoho'),
|
|
|
path: `[${req.method}]${routeEncode.getRouter(req)}`,
|
|
|
};
|
|
|
};
|
|
|
|
|
|
const handlerError = (err = {}, req, res, next) => {
|
|
|
if (err.code === 404) {
|
|
|
return res.redirect('/error/404');
|
|
|
} else if (err.code === 500) {
|
|
|
return res.redirect('/error/500');
|
|
|
}
|
|
|
console.log(err)
|
|
|
return next(err);
|
|
|
};
|
|
|
|
|
|
const getCacheKey = (urlPath, cackeKey = '') => {
|
|
|
const urlObj = url.parse(urlPath);
|
|
|
|
...
|
...
|
@@ -54,37 +68,11 @@ const getCacheKey = (urlPath, cackeKey = '') => { |
|
|
.replace('$params', urlObj.query));
|
|
|
};
|
|
|
|
|
|
const parseError = async({stack = ''}) => {
|
|
|
try {
|
|
|
const splits = stack.split('\n');
|
|
|
const lastError = splits.map(str => {
|
|
|
const match = str.match(REG_STACK);
|
|
|
|
|
|
if (match) {
|
|
|
return {file: match[1], line: parseInt(match[2], 10), column: parseInt(match[3], 10)};
|
|
|
}
|
|
|
return false;
|
|
|
}).find(match => match);
|
|
|
|
|
|
if (lastError && lastError.file) {
|
|
|
const consumer = await new sourceMap.SourceMapConsumer(serverBundle.maps[lastError.file]);
|
|
|
|
|
|
const origin = consumer.originalPositionFor({
|
|
|
line: lastError.line,
|
|
|
column: 342
|
|
|
});
|
|
|
|
|
|
console.log(origin);
|
|
|
}
|
|
|
} catch (error) {
|
|
|
logger.error(error);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
const render = (route) => {
|
|
|
return async(req, res, next) => {
|
|
|
try {
|
|
|
res.setHeader('X-YOHO-Version', pkg.version);
|
|
|
const ck = getCacheKey(req.url, route.cackeKey);
|
|
|
const ck = route.cackeKey ? getCacheKey(req.url, route.cackeKey) : void 0;
|
|
|
|
|
|
if (config.useCache && route.cache && ck) {
|
|
|
const html = await redis.getAsync(ck);
|
...
|
...
|
@@ -100,20 +88,23 @@ const render = (route) => { |
|
|
|
|
|
renderer.renderToString(context, (err, html) => {
|
|
|
if (err) {
|
|
|
parseError(err);
|
|
|
return next(err.message);
|
|
|
return handlerError(err, req, res, next);
|
|
|
}
|
|
|
if (config.useCache && route.cache && ck) {
|
|
|
redis.setex(ck, route.cacheTime || 60, html);
|
|
|
}
|
|
|
return res.send(html);
|
|
|
});
|
|
|
} catch (error) {
|
|
|
return next(error);
|
|
|
}
|
|
|
};
|
|
|
};
|
|
|
const devRender = (route) => {
|
|
|
return async(req, res, next) => {
|
|
|
try {
|
|
|
res.setHeader('X-YOHO-Version', pkg.version);
|
|
|
const ck = getCacheKey(req.url, route.cackeKey);
|
|
|
const ck = route.cackeKey ? getCacheKey(req.url, route.cackeKey) : void 0;
|
|
|
|
|
|
if (config.useCache && route.cache && ck) {
|
|
|
const html = await redis.getAsync(ck);
|
...
|
...
|
@@ -132,19 +123,12 @@ const devRender = (route) => { |
|
|
process.removeListener('message', event);
|
|
|
if (msg.action === 'ssr_request') {
|
|
|
if (msg.err) {
|
|
|
try {
|
|
|
const err = JSON.parse(msg.err);
|
|
|
let err = msg.err;
|
|
|
|
|
|
if (err.code === 404) {
|
|
|
return next();
|
|
|
}
|
|
|
return next(err);
|
|
|
} catch (e) {
|
|
|
return next({
|
|
|
code: 500,
|
|
|
message: msg.err
|
|
|
});
|
|
|
}
|
|
|
try {
|
|
|
err = JSON.parse(msg.err);
|
|
|
} catch (error) {} // eslint-disable-line
|
|
|
return handlerError(err, req, res, next);
|
|
|
}
|
|
|
if (config.useCache && route.cache && ck) {
|
|
|
redis.setex(ck, route.cacheTime || 60, msg.html);
|
...
|
...
|
@@ -154,6 +138,9 @@ const devRender = (route) => { |
|
|
};
|
|
|
|
|
|
process.on('message', event);
|
|
|
} catch (error) {
|
|
|
return next(error);
|
|
|
}
|
|
|
};
|
|
|
};
|
|
|
|
...
|
...
|
@@ -165,4 +152,6 @@ _.each(routes, r => { |
|
|
}
|
|
|
});
|
|
|
|
|
|
module.exports = router; |
|
|
exports.ssrRender = isDev ? devRender({}) : render({});
|
|
|
|
|
|
exports.routers = router; |
...
|
...
|
|