主要需要遵循相關的規(guī)范
- 插件命名規(guī)范
- 實現規(guī)范, 提供
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
// 組件上的靜態(tài)方法 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);
// 創(chuàng)建一個react element
const element = React.createElement(reactElement, locals);
// 使用ssr 核心API renderToString
return ReactDOMServer.renderToString(element);
}