主要需要遵循相关的规范
- 插件命名规范
- 实现规范, 提供
render renderToString
方法实现。
以egg-view-react-ssr实现 为例
// 声明一个View 类
class View {
constructor(ctx) {
this.ctx = ctx;
this.app = ctx.app;
}
// 实现render方法,用于渲染模板文件
async render(name, locals, options = {}) {
// 合并数据
locals = this.app.react.mergeLocals(this.ctx, locals, options);
try {
return await this.app.react.renderPage(name, locals, options);
} catch (err) {
// egg-view-react-ssr 的配置项
const config = this.app.config.reactssr;
// 降级客户端渲染
if (config.fallbackToClient) {
const layout = options.layout || config.layout;
this.app.logger.error('[%s] server render bundle error, try client render, the server render error', name, err);
options = Object.assign({}, options, { markup: true });
return await this.app.react.renderPage(layout, locals, options);
}
/* istanbul ignore next */
throw err;
}
}
/* eslint no-unused-vars:off */
/* istanbul ignore next */
// 实现renderString方法,用于渲染字符串模板
renderString(tpl, locals) {
return Promise.reject('not implemented yet!');
}
}
module.exports = View;
最终都是调用this.app.react.react.renderPage
方法。
this.app.react
对象是通过egg
的扩展机制,对application
的原型进行的扩展
const Engine = require('../../lib/engine');
const REACT_ENGINE = Symbol('Application#react');
module.exports = {
get react() {
if (!this[REACT_ENGINE]) {
this[REACT_ENGINE] = new Engine(this);
}
return this[REACT_ENGINE];
},
};
Engine class renderPage
逻辑
// 渲染实现的核心逻辑
async renderPage(name, locals, options) {
// 支持自定义 layout html 模板
// 1. CSR 使用模板
// 2. 服务端渲染 获取对应的react组件
const html = /\.(html|htm|tpl)$/.test(name) ? await this.readFile(name) : await this.render(name, locals, options);
// this.app.react.resource 来自 server-side-render-resource
// 主要实现了 css js window.__INITIAL_STATE__ 等注入
if (this.app.react.resource) {
locals = this.normalizeLocals(locals);
return this.app.react.resource.inject(html, options.name, locals, options);
}
return html;
}
readFile
逻辑
// 服务端渲染模板的时候 输出模板文件
// 因为Engine类是挂载到app上面,而app是全局单例,所以此处也只存在一个
// 模板不存在太多的情况下 不会出现因为缓存模板,内存占用太高的问题
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);
}
});
});
}
render
实现逻辑
async render(name, locals, options) {
// 加载对应的组件(已经通过webpack打包成了 commonjs模块)
const reactElement = require(name);
return this.renderElement(reactElement, locals, options);
}
renderElement
的实现逻辑
async renderElement(reactElement, locals, options) {
reactElement = this.normalizeReactElement(reactElement);
// support asyncData
// 组件上的静态方法 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);
}
renderToString
的实现逻辑
renderToString(reactElement, locals) {
reactElement = this.normalizeReactElement(reactElement);
// 创建一个react element
const element = React.createElement(reactElement, locals);
// 使用ssr 核心API renderToString
return ReactDOMServer.renderToString(element);
}
参考
- egg view-plugin