You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

82 lines
2.4 KiB
JavaScript

"use strict";
const Promise = require("bluebird");
const React = require("react");
const ReactDOMServer = require("react-dom/server");
const defaultValue = require("default-value");
const dotty = require("dotty");
const registerBabel = require("./register-babel");
const clearRequireCache = require("./clear-require-cache");
/* FILEBUG: Express does not copy symbol-keyed properties from app.locals, but it probably should? */
// let ExpressReact = Symbol("ExpressReact");
let ExpressReact = "__EXPRESS_REACT_SETTINGS";
let LocalsContext = React.createContext({});
function componentAtPath(moduleRoot, componentPath) {
if (componentPath == null) {
return moduleRoot;
} else {
return dotty.get(moduleRoot, componentPath);
}
}
function renderComponent(component, locals, doctype = "<!DOCTYPE html>") {
let tree = React.createElement(LocalsContext.Provider, {value: locals},
React.createElement(component, locals)
);
try {
/* TODO: Expose internals? Like koa-react */
return doctype + ReactDOMServer.renderToStaticMarkup(tree);
} catch (err) {
if (/Element type is invalid:/.test(err.message)) {
throw new Error(`Expected a React component, but got '${component}' - maybe you forgot to specify a componentPath?`);
} else {
throw err;
}
}
}
module.exports = {
Settings: ExpressReact,
LocalsContext: LocalsContext,
createEngine: function createEngine({ prepare } = {}) {
let babelRegistered = false;
return function asyncRender(filename, options, callback) {
return Promise.try(() => {
let viewPaths = options.settings.views;
if (!babelRegistered) {
registerBabel(viewPaths);
babelRegistered = true;
}
let {componentPath} = defaultValue(options[ExpressReact], {});
return Promise.try(() => {
let templateFile = require(filename);
let moduleRoot = defaultValue(templateFile.exports, templateFile);
return Promise.try(() => {
if (prepare != null) {
return prepare(moduleRoot, options);
}
}).then((result) => {
console.log("QUERY RESULT:", require("util").inspect(result, { colors: true, depth: null }));
let mergedOptions = Object.assign({}, options, result);
return renderComponent(componentAtPath(moduleRoot, componentPath), mergedOptions);
});
}).finally(() => {
if (options.settings.env === "development") {
clearRequireCache(viewPaths);
}
});
}).asCallback(callback);
};
}
};