Skip to content

Commit

Permalink
refactor functions out of handler file
Browse files Browse the repository at this point in the history
remove slash
  • Loading branch information
jchip committed Aug 3, 2018
1 parent 6d53d09 commit 82495cb
Show file tree
Hide file tree
Showing 7 changed files with 308 additions and 135 deletions.
3 changes: 1 addition & 2 deletions packages/electrode-react-webapp/lib/async-template.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ const { resolvePath } = require("./utils");
const Token = require("./token");
const stringArray = require("string-array");
const Path = require("path");
const slash = require("slash");

const { TEMPLATE_DIR } = require("./symbols");

Expand Down Expand Up @@ -78,7 +77,7 @@ class AsyncTemplate {
_parseTemplate(template, filepath) {
const tokens = [];
let pt = 0;
const templateDir = slash(Path.dirname(filepath));
const templateDir = Path.dirname(filepath);

while (true) {
const mx = template.substr(pt).match(tokenOpenTagRegex);
Expand Down
64 changes: 64 additions & 0 deletions packages/electrode-react-webapp/lib/react/content.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"use strict";
const HttpStatusCodes = require("http-status-codes");

const HTTP_ERROR_500 = 500;

async function getContent(renderSs, options, context) {
let userContent = options.content;

// prepare user content for container of SSR output

if (typeof userContent === "string") {
return { status: 200, html: userContent };
}

if (typeof userContent !== "function") return userContent;

if (!renderSs) return { status: 200, html: "<!-- noss mode -->" };

// invoke user content as a function, which could return any content
// as static html or generated from react's renderToString
userContent = userContent(options.request, options, context);
if (userContent.then) {
try {
// user function needs to generate the content async, so wait for it.
return await userContent;
} catch (err) {
if (!err.status) err.status = HTTP_ERROR_500;
throw err;
}
}

return userContent;
}

function transformOutput(result, context) {
const content = context.user.content;
if (content && content.status !== HttpStatusCodes.OK) {
return {
status: content.status,
path: content.path,
store: content.store,
html: result
};
}

return result;
}

const htmlifyScripts = (scripts, scriptNonce) => {
return scripts
.map(
x =>
typeof x === "string"
? `<script${scriptNonce}>${x}</script>\n`
: x.map(n => `<script src="${n.src}"></script>`).join("\n")
)
.join("\n");
};

module.exports = {
getContent,
transformOutput,
htmlifyScripts
};
175 changes: 46 additions & 129 deletions packages/electrode-react-webapp/lib/react/token-handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@

/* eslint-disable max-statements, max-depth */

const _ = require("lodash");
const groupScripts = require("../group-scripts");
const HttpStatusCodes = require("http-status-codes");

const HTTP_ERROR_500 = 500;
const {
getIconStats,
getCriticalCSS,
getDevCssBundle,
getDevJsBundle,
getProdBundles,
processRenderSsMode,
getCspNonce
} = require("../utils");

const utils = require("../utils");

const getIconStats = utils.getIconStats;
const getCriticalCSS = utils.getCriticalCSS;
const { getContent, transformOutput, htmlifyScripts } = require("./content");

const CONTENT_MARKER = "SSR_CONTENT";
const HEADER_BUNDLE_MARKER = "WEBAPP_HEADER_BUNDLES";
Expand Down Expand Up @@ -71,137 +74,51 @@ module.exports = function setup(handlerContext /* , asyncTemplate */) {
}
};

const htmlifyScripts = (scripts, scriptNonce) => {
return scripts
.map(
x =>
typeof x === "string"
? `<script${scriptNonce}>${x}</script>\n`
: x.map(n => `<script src="${n.src}"></script>`).join("\n")
)
.join("\n");
};

const transformOutput = (result, context) => {
const content = context.user.content;
if (content && content.status !== HttpStatusCodes.OK) {
return {
status: content.status,
path: content.path,
store: content.store,
html: result
};
}

return result;
};

const INITIALIZE = async context => {
const options = context.options;
const request = options.request;
const mode = options.mode;
let renderSs = RENDER_SS;
if (renderSs) {
if (mode === "noss") {
renderSs = false;
} else if (renderSs === "datass" || mode === "datass") {
// signal user content callback to populate prefetch data only and skips actual SSR
_.set(request, ["app", "ssrPrefetchOnly"], true);
}
}
const renderSs = processRenderSsMode(request, RENDER_SS, mode);

const prepareContext = content => {
if (content.render === false || content.html === undefined) {
return context.voidStop(content);
}

let cspScriptNonce;
let cspStyleNonce;
if (routeOptions.cspNonceValue !== undefined) {
const nonceObject = routeOptions.cspNonceValue;
if (typeof nonceObject === "function") {
cspScriptNonce = nonceObject(request, "script");
cspStyleNonce = nonceObject(request, "style");
} else {
cspScriptNonce = _.get(request, nonceObject.script, undefined);
cspStyleNonce = _.get(request, nonceObject.style, undefined);
}
}

const chunkNames = chunkSelector(request);

let devCSSBundle;
if (chunkNames.css) {
const cssChunks = Array.isArray(chunkNames.css) ? chunkNames.css : [chunkNames.css];
devCSSBundle = _.map(cssChunks, chunkName => `${devBundleBase}${chunkName}.style.css`);
} else {
devCSSBundle = [`${devBundleBase}style.css`];
}

const devJSBundle = chunkNames.js
? `${devBundleBase}${chunkNames.js}.bundle.dev.js`
: `${devBundleBase}bundle.dev.js`;
const jsChunk = _.find(assets.js, asset => _.includes(asset.chunkNames, chunkNames.js));
const cssChunk = _.filter(assets.css, asset =>
_.some(asset.chunkNames, assetChunkName => _.includes(chunkNames.css, assetChunkName))
);
const scriptNonce = cspScriptNonce ? ` nonce="${cspScriptNonce}"` : "";
const styleNonce = cspStyleNonce ? ` nonce="${cspStyleNonce}"` : "";

const renderJs = RENDER_JS && mode !== "nojs";

context.setOutputTransform(transformOutput);
context.user = {
request: options.request,
response: {
headers: {}
},
routeOptions,
routeData,
content,
mode,
renderJs,
renderSs,
scriptNonce,
styleNonce,
chunkNames,
devCSSBundle,
devJSBundle,
jsChunk,
cssChunk
};

return undefined;
};
const content = await getContent(renderSs, options, context);

let userContent = options.content;

// prepare user content for container of SSR output

if (typeof userContent === "function") {
if (renderSs) {
// invoke user content as a function, which could return any content
// as static html or generated from react's renderToString
userContent = userContent(options.request, options, context);
if (userContent.then) {
try {
// user function needs to generate the content async, so wait for it.
return prepareContext(await userContent);
} catch (err) {
if (!err.status) err.status = HTTP_ERROR_500;
throw err;
}
}
} else {
userContent = { status: 200, html: "<!-- noss mode -->" };
}
} else if (typeof userContent === "string") {
userContent = { status: 200, html: userContent };
if (content.render === false || content.html === undefined) {
return context.voidStop(content);
}

prepareContext(userContent);
const chunkNames = chunkSelector(request);

const devCSSBundle = getDevCssBundle(chunkNames, routeData);
const devJSBundle = getDevJsBundle(chunkNames, routeData);

const { jsChunk, cssChunk } = getProdBundles(chunkNames, routeData);
const { scriptNonce, styleNonce } = getCspNonce(request, routeOptions.cspNonceValue);

const renderJs = RENDER_JS && mode !== "nojs";

context.setOutputTransform(transformOutput);

context.user = {
request: options.request,
response: {
headers: {}
},
routeOptions,
routeData,
content,
mode,
renderJs,
renderSs,
scriptNonce,
styleNonce,
chunkNames,
devCSSBundle,
devJSBundle,
jsChunk,
cssChunk
};

return "";
return context;
};

const tokenHandlers = {
Expand Down
70 changes: 69 additions & 1 deletion packages/electrode-react-webapp/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,69 @@ ${errMsg()}
</pre></body></html>`;
}

function getDevCssBundle(chunkNames, routeData) {
const devBundleBase = routeData.devBundleBase;
if (chunkNames.css) {
const cssChunks = Array.isArray(chunkNames.css) ? chunkNames.css : [chunkNames.css];
return _.map(cssChunks, chunkName => `${devBundleBase}${chunkName}.style.css`);
} else {
return [`${devBundleBase}style.css`];
}
}

function getDevJsBundle(chunkNames, routeData) {
const devBundleBase = routeData.devBundleBase;

return chunkNames.js
? `${devBundleBase}${chunkNames.js}.bundle.dev.js`
: `${devBundleBase}bundle.dev.js`;
}

function getProdBundles(chunkNames, routeData) {
const assets = routeData.assets;

return {
jsChunk: _.find(assets.js, asset => _.includes(asset.chunkNames, chunkNames.js)),

cssChunk: _.filter(assets.css, asset =>
_.some(asset.chunkNames, assetChunkName => _.includes(chunkNames.css, assetChunkName))
)
};
}

function processRenderSsMode(request, renderSs, mode) {
if (renderSs) {
if (mode === "noss") {
return false;
} else if (renderSs === "datass" || mode === "datass") {
renderSs = "datass";
// signal user content callback to populate prefetch data only and skips actual SSR
_.set(request, ["app", "ssrPrefetchOnly"], true);
}
}

return renderSs;
}

function getCspNonce(request, cspNonceValue) {
let scriptNonce = "";
let styleNonce = "";

if (cspNonceValue) {
if (typeof cspNonceValue === "function") {
scriptNonce = cspNonceValue(request, "script");
styleNonce = cspNonceValue(request, "style");
} else {
scriptNonce = _.get(request, cspNonceValue.script);
styleNonce = _.get(request, cspNonceValue.style);
}
scriptNonce = scriptNonce ? ` nonce="${scriptNonce}"` : "";
styleNonce = styleNonce ? ` nonce="${styleNonce}"` : "";
}

return { scriptNonce, styleNonce };
}

const resolvePath = path => (!Path.isAbsolute(path) ? Path.resolve(path) : path);

module.exports = {
Expand All @@ -131,5 +194,10 @@ module.exports = {
getCriticalCSS,
getStatsPath,
resolvePath,
htmlifyError
htmlifyError,
getDevCssBundle,
getDevJsBundle,
getProdBundles,
processRenderSsMode,
getCspNonce
};
1 change: 0 additions & 1 deletion packages/electrode-react-webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@
"in-publish": "^2.0.0",
"lodash": "^4.17.10",
"require-at": "^1.0.0",
"slash": "^2.0.0",
"string-array": "^1.0.0"
},
"devDependencies": {
Expand Down
Loading

0 comments on commit 82495cb

Please sign in to comment.