-
Notifications
You must be signed in to change notification settings - Fork 9
/
engine.js
143 lines (127 loc) · 4.61 KB
/
engine.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
'use strict';
const fs = require('fs');
const React = require('react');
const serialize = require('serialize-javascript');
const ReactDOMServer = require('react-dom/server');
const Resource = require('server-side-render-resource');
const REACT_RESOURCE = Symbol('Application#resource');
class Engine {
constructor(app) {
this.app = app;
this.config = app.config.reactssr;
this.fileCache = [];
}
get resource() {
if (!this[REACT_RESOURCE]) {
if (fs.existsSync(this.config.manifest)) {
this[REACT_RESOURCE] = new Resource(this.app, this.config);
}
}
if (process.env && process.env.NODE_ENV === 'production' && !this[REACT_RESOURCE]) {
this.app.logger.warn('[egg-view-react-ssr] react server rendering missing manifest!');
}
return this[REACT_RESOURCE];
}
getAsset(name, state) {
const manifest = this.resource && this.resource.manifest || {};
const deps = manifest.deps || {};
const res = deps[name] || {};
return {
js: res.js || [],
css: res.css || [],
state: serialize(state || {}, { isJSON: true }),
};
}
normalizeLocals(locals = {}) {
[ 'ctx', 'request', 'helper' ].forEach(key => {
Object.defineProperty(locals, key, { enumerable: false });
});
return locals;
}
mergeLocals(ctx, locals, options, engine = true) {
options = options || {};
locals = engine ? options.locals : locals;
locals = this.setCSRFLocals(ctx, locals);
if (this.config.mergeLocals) {
// if egg-view engine mode, the locals had merged
return Object.assign({}, { ctx, request: ctx.request, helper: ctx.helper }, ctx.locals, locals);
}
return Object.assign({}, { ctx, request: ctx.request, helper: ctx.helper }, locals);
}
setCSRFLocals(ctx, locals) {
// when csrf enable, set ctx csrf
const security = this.app.config.security;
if (security.csrf && security.csrf.enable) {
return Object.assign({}, { csrf: ctx.csrf }, locals);
}
return locals;
}
normalizeReactElement(reactElement) {
return reactElement && reactElement.default ? reactElement.default : reactElement;
}
async render(name, locals, options) {
const reactElement = require(name);
return this.renderElement(reactElement, locals, options);
}
async readFile(filepath) {
if (this.fileCache[filepath]) {
return this.fileCache[filepath];
}
return new Promise((resolve, reject) => {
fs.readFile(filepath, 'utf8', (err, data) => {
if (err) {
reject(err);
} else {
this.fileCache[filepath] = data;
resolve(data);
}
});
});
}
async renderPage(name, locals, options) {
// 支持自定义 layout html 模板
const html = /\.(html|htm|tpl)$/.test(name) ? await this.readFile(name) : await this.render(name, locals, options);
if (this.app.react.resource) {
locals = this.normalizeLocals(locals);
return this.app.react.resource.inject(html, options.name, locals, options);
}
return html;
}
// eslint-disable-next-line no-unused-vars
async renderElement(reactElement, locals, options) {
reactElement = this.normalizeReactElement(reactElement);
// support asyncData
if (reactElement.asyncData) {
const data = await reactElement.asyncData(locals);
locals = Object.assign(locals, data);
return this.renderToString(reactElement, locals);
}
return this.renderToString(reactElement, locals);
}
async renderAsset(ctx, name, locals, options = {}) {
const layout = options.layout || this.config.layout;
const viewEngine = options.viewEngine || this.config.viewEngine || 'nunjucks';
// 输出到页面的 state 数据
const state = Object.assign({}, ctx.locals, locals);
const asset = this.getAsset(name, state);
// egg-view 自动合并 ctx, request, response, helper
const template = await this.readFile(layout);
const context = Object.assign({}, locals, { asset });
return ctx.renderString(template, context, { viewEngine });
}
renderMarkup(name, locals) {
const reactElement = require(name);
return Promise.resolve(this.renderToStaticMarkup(reactElement, locals));
}
renderToString(reactElement, locals) {
reactElement = this.normalizeReactElement(reactElement);
const element = React.createElement(reactElement, locals);
return ReactDOMServer.renderToString(element);
}
renderToStaticMarkup(reactElement, locals) {
reactElement = this.normalizeReactElement(reactElement);
const element = React.createElement(reactElement, locals);
return ReactDOMServer.renderToStaticMarkup(element);
}
}
module.exports = Engine;