Skip to content

Commit

Permalink
add templateProcessor option (#896)
Browse files Browse the repository at this point in the history
  • Loading branch information
jchip authored Aug 6, 2018
1 parent 37c91f4 commit 0bd5a19
Show file tree
Hide file tree
Showing 13 changed files with 1,255 additions and 328 deletions.
174 changes: 162 additions & 12 deletions packages/electrode-react-webapp/lib/async-template.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use strict";

/* eslint-disable max-statements, no-constant-condition, no-magic-numbers */
/* eslint-disable max-params, max-statements, no-constant-condition, no-magic-numbers */

const assert = require("assert");
const Fs = require("fs");
Expand Down Expand Up @@ -31,12 +31,17 @@ class AsyncTemplate {
this._tokenHandlers = [];
this._handlersMap = {};
this._initializeTemplate(options.htmlFile);
this._initializeTokenHandlers([].concat(options.tokenHandlers).filter(x => x));
this._applyTokenLoad();
this._renderer = new Renderer({
htmlTokens: this._tokens,
tokenHandlers: this._tokenHandlers
});
this._initializeTokenHandlers([].concat(this._options.tokenHandlers).filter(x => x));
}

initializeRenderer(reset) {
if (reset || !this._renderer) {
this._applyTokenLoad();
this._renderer = new Renderer({
htmlTokens: this._tokens,
tokenHandlers: this._tokenHandlers
});
}
}

get tokens() {
Expand Down Expand Up @@ -65,13 +70,158 @@ class AsyncTemplate {
return context;
}

/*
break up the template into a list of literal strings and the tokens between them
- each item is of the form:
_findTokenIndex(id, str, index, instance = 0, msg = "AsyncTemplate._findTokenIndex") {
let found;

if (id) {
found = this.findTokensById(id, instance + 1);
} else if (str) {
found = this.findTokensByStr(str, instance + 1);
} else if (!Number.isInteger(index)) {
throw new Error(`${msg}: invalid id, str, and index`);
} else if (index < 0 || index >= this._tokens.length) {
throw new Error(`${msg}: index ${index} is out of range.`);
} else {
return index;
}

if (found.length === 0) return false;

return found[instance].index;
}

//
// add tokens at first|last position of the tokens,
// or add tokens before|after token at {id}[instance] or {index}
// ^^^ {insert}
// - Note that item indexes will change after add
//
// returns:
// - number of tokens removed
// - false if nothing was removed
// throws:
// - if id and index are invalid
// - if {insert} is invalid
//
addTokens({ insert = "after", id, index, str, instance = 0, tokens }) {
const create = tk => {
return new Token(
tk.token,
-1,
typeof tk.props === "string" ? this._parseTokenProps(tk.props) : tk.props
);
};

{ str: "literal string" }
if (insert === "first") {
this._tokens.unshift(...tokens.map(create));
return 0;
}

if (insert === "last") {
const x = this._tokens.length;
this._tokens.push(...tokens.map(create));
return x;
}

index = this._findTokenIndex(id, str, index, instance, "AsyncTemplate.addTokens");
if (index === false) return false;

if (insert === "before") {
this._tokens.splice(index, 0, ...tokens.map(create));
return index;
}

or a Token object
if (insert === "after") {
index++;
this._tokens.splice(index, 0, ...tokens.map(create));
return index;
}

throw new Error(
`AsyncTemplate.addTokens: insert "${insert}" is not valid, must be first|before|after|last`
);
}

//
// remove {count} tokens before|after token at {id}[instance] or {index}
// ^^^ {remove}
// - if removeSelf is true then the token at {id}[instance] or {index} is included for removal
// returns:
// - array of tokens removed
// throws:
// - if id and index are invalid
// - if {remove} is invalid
//
removeTokens({ remove = "after", removeSelf = true, id, str, index, instance = 0, count = 1 }) {
assert(count > 0, `AsyncTemplate.removeTokens: count ${count} must be > 0`);

index = this._findTokenIndex(id, str, index, instance, "AsyncTemplate.removeTokens");
if (index === false) return false;

const offset = removeSelf ? 0 : 1;

if (remove === "before") {
let newIndex = index + 1 - count - offset;
if (newIndex < 0) {
newIndex = 0;
count = index + 1 - offset;
}
return this._tokens.splice(newIndex, count);
} else if (remove === "after") {
return this._tokens.splice(index + offset, count);
} else {
throw new Error(`AsyncTemplate.removeTokens: remove "${remove}" must be before|after`);
}
}

findTokensById(id, count = Infinity) {
if (!Number.isInteger(count)) count = this._tokens.length;

const found = [];

for (let index = 0; index < this._tokens.length && found.length < count; index++) {
const token = this._tokens[index];
if (token.id === id) {
found.push({ index, token });
}
}

return found;
}

findTokensByStr(matcher, count = Infinity) {
if (!Number.isInteger(count)) count = this._tokens.length;

const found = [];

let match;

if (typeof matcher === "string") {
match = str => str.indexOf(matcher) >= 0;
} else if (matcher && matcher.constructor.name === "RegExp") {
match = str => str.match(matcher);
} else {
throw new Error("AsyncTemplate.findTokensByStr: matcher must be a string or RegExp");
}

for (let index = 0; index < this._tokens.length && found.length < count; index++) {
const token = this._tokens[index];
if (token.hasOwnProperty("str") && match(token.str)) {
found.push({ index, token });
}
}

return found;
}

/*
* break up the template into a list of literal strings and the tokens between them
*
* - each item is of the form:
*
* { str: "literal string" }
*
* or a Token object
*/

_parseTemplate(template, filepath) {
Expand Down
14 changes: 9 additions & 5 deletions packages/electrode-react-webapp/lib/react-webapp.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ const _ = require("lodash");
const Path = require("path");
const AsyncTemplate = require("./async-template");

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

const resolveChunkSelector = utils.resolveChunkSelector;
const loadAssetsFromStats = utils.loadAssetsFromStats;
const getStatsPath = utils.getStatsPath;
const {
resolveChunkSelector,
loadAssetsFromStats,
getStatsPath,
invokeTemplateProcessor
} = require("./utils");

function makeRouteHandler(routeOptions) {
const userTokenHandlers = [].concat(routeOptions.tokenHandler, routeOptions.tokenHandlers);
Expand All @@ -29,6 +30,9 @@ function makeRouteHandler(routeOptions) {
routeOptions
});

invokeTemplateProcessor(asyncTemplate, routeOptions);
asyncTemplate.initializeRenderer();

return options => {
return asyncTemplate.render(options);
};
Expand Down
2 changes: 1 addition & 1 deletion packages/electrode-react-webapp/lib/token.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class Token {

// if token is a module, then load it
load(options) {
if (!this.isModule) return;
if (!this.isModule || this.custom) return;

let tokenMod = viewTokenModules[this.id];

Expand Down
34 changes: 33 additions & 1 deletion packages/electrode-react-webapp/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
const _ = require("lodash");
const fs = require("fs");
const Path = require("path");
const requireAt = require("require-at");
const assert = require("assert");

/**
* Tries to import bundle chunk selector function if the corresponding option is set in the
Expand Down Expand Up @@ -201,6 +203,34 @@ function responseForBadStatus(request, routeOptions, data) {
};
}

function loadFuncFromModule(modulePath, exportFuncName, requireAtDir) {
const mod = requireAt(requireAtDir || process.cwd())(modulePath);
const exportFunc = (exportFuncName && mod[exportFuncName]) || mod;
assert(
typeof exportFunc === "function",
`loadFuncFromModule ${modulePath} doesn't export a usable function`
);
return exportFunc;
}

function invokeTemplateProcessor(asyncTemplate, routeOptions) {
const tp = routeOptions.templateProcessor;

if (tp) {
let tpFunc;
if (typeof tp === "string") {
tpFunc = loadFuncFromModule(tp, "templateProcessor");
} else {
tpFunc = tp;
assert(typeof tpFunc === "function", `templateProcessor is not a function`);
}

return tpFunc(asyncTemplate, routeOptions);
}

return undefined;
}

module.exports = {
resolveChunkSelector,
loadAssetsFromStats,
Expand All @@ -215,5 +245,7 @@ module.exports = {
processRenderSsMode,
getCspNonce,
responseForError,
responseForBadStatus
responseForBadStatus,
loadFuncFromModule,
invokeTemplateProcessor
};
1 change: 1 addition & 0 deletions packages/electrode-react-webapp/test/data/template2.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<script>
console.log("test")
</script>
<!--%{webapp-body-bundles}-->
<!--%{meta-tags}-->
</head>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"use strict";

module.exports = function() {
return "template-processor-1";
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"use strict";

module.exports = {
templateProcessor: function() {
return "template-processor-2";
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"use strict";

module.exports = {
oops: function() {
return "template-processor-3";
}
};
Loading

0 comments on commit 0bd5a19

Please sign in to comment.