generator-wxnode-boilerplate
Version:
Yeoman generator for wxnode boilerplate
132 lines (116 loc) • 4.44 kB
text/typescript
import {BundleRenderer, createBundleRenderer} from 'vue-server-renderer';
import fs from 'fs';
import {infoL} from '../@core/log/log';
import path from 'path';
import * as Cached from '../@core/cache';
import to from 'await-to-js';
import {getIp} from '../util';
import {ICustomRouterContext} from '../@types/types';
import {errL} from '../@core/log/log';
import {IdKey} from '../config/idKey';
import {reportIdKey} from '../@core/report/idkey';
import {clientRender} from './render-client';
const isProd = process.env.NODE_ENV === 'production';
const isDebugServer = process.env.DEBUG === 'server';
const resolve = file => path.resolve(process.cwd(), file);
export function createRenderer(bundle, options) {
// https://github.com/vuejs/vue/blob/dev/packages/vue-server-renderer/README.md#why-use-bundlerenderer
return createBundleRenderer(bundle, Object.assign(options, {
// this is only needed when vue-server-renderer is npm-linked
basedir: resolve('./dist'),
// recommended for performance
runInNewContext: false,
}));
}
let renderer: BundleRenderer;
let readyPromise: Promise<any>;
let htmlTpl: string;
const templatePath = resolve('./public/index.template.html');
export function initServerRender(app) {
if (isProd || isDebugServer) {
// In production: create server renderer using template and built server bundle.
// The server bundle is generated by vue-ssr-webpack-plugin.
const template = fs.readFileSync(templatePath, 'utf-8');
const bundle = require(resolve('./dist/vue-ssr-server-bundle.json'));
// The client manifests are optional, but it allows the renderer
// to automatically infer preload/prefetch links and directly add <script>
// tags for any async chunks used during render, avoiding waterfall requests.
const clientManifest = require(resolve('./dist/vue-ssr-client-manifest.json'));
renderer = createRenderer(bundle, {
template,
clientManifest,
});
} else {
// In development: setup the dev server with watch and hot-reload,
// and create a new renderer on bundle / index template update.
readyPromise = require(resolve('./build/setup-dev-server'))(
app,
templatePath,
(bundle, options) => {
htmlTpl = options.htmlTpl;
renderer = createRenderer(bundle, options);
},
);
}
}
export async function render(ctx: ICustomRouterContext) {
ctx.set('Content-Type', 'text/html');
const isHit = await Cached.getCacheAndResponse(ctx.url, ctx, Cached.CacheType.HTML);
if (isHit) {
infoL(`直出页面缓存命中: ${ctx.url}`);
ctx.fromCached = true;
return;
}
// 无缓存,服务端渲染
const ip = getIp(ctx);
const context = {
url: ctx.url,
cookie: ctx.request.header.cookie,
ua: ctx.request.headers['user-agent'],
ip,
};
const [err, html] = await to(renderer.renderToString(context));
if (err) throw err;
ctx.body = html;
// 缓存
Cached.set(ctx.url, html, Cached.emCACHE.Cache_Template);
}
export function getReadyPromise(): Promise<any> {
return readyPromise;
}
export function getStaticHtml(): string {
return htmlTpl;
}
// Server render出错时可以回退到 Client render
const serverRenderReadyPromise = getReadyPromise();
export async function serverRender(ctx){
if(isProd){
try {
await render(ctx);
} catch (e) {
// 同构出错,使用客户端渲染
errL('同构直出失败, 降级为前端渲染:', e);
reportIdKey(IdKey.server_render_fail);
if (e && e.code === 404) {
// 404的情况直接返回404页面
throw e;
}
return clientRender(ctx);
}
}else{
try {
await serverRenderReadyPromise;
await render(ctx);
} catch (e) {
// 同构出错,使用客户端渲染
if (e && e.code === 404) {
// 404的情况直接返回404页面
throw e;
} else {
errL('同构直出失败, 降级为前端渲染:', e);
reportIdKey(IdKey.server_render_fail);
}
return clientRender(ctx);
}
}
}