Skip to content

Instantly share code, notes, and snippets.

@dfkaye
Last active November 23, 2018 20:50
Show Gist options
  • Select an option

  • Save dfkaye/2d5cbd87f6bbf4976762 to your computer and use it in GitHub Desktop.

Select an option

Save dfkaye/2d5cbd87f6bbf4976762 to your computer and use it in GitHub Desktop.
"Stateless components" are really string#replace (plus a state/dom side-effect) in vanilla.js
// 15 Mar 2016
// saw this
// export default React => (props) => <h1>{ props.title }</h1>;
// at
// https://github.com/ericelliott/react-pure-component-starter#react-pure-component-starter
// and thought,
// "that's just string#replace (plus a state or dom side-effect somewhere...)"
// then, after writing the implementation below, I realized,
// "I like the JSX {props...} pattern BETTER than template literal interpolations `${props...}`"
// and,
// "I prefer the multi-line string syntax using `\` over template literals..."
//
// 23 Apr 2016: modify template() to return { valueOf: function(){}, toString: function react(...) {...} }
//
// 27 Oct 2016: now I prefer the ${props...} look over the bare {props...} style.
// 18 November 2018 - About time to finish this. Not awesome but distinctly better.
// + Convert to string.template(props);
// + No more need for "props.whatever", just traverse keys from the data passed in
// + Keep the literal if the resolved path returns undefined; replace the literal if value is null.
// + Add ? for quiet references (return empty string if a '?' appears and the result is undefined)
// 23 November 2018
// + 'class' test
// + 'is.quiet.undefined' test
// + added Object.prototype.template(s) definition
var props = {
some: { title: 'grooovy' },
another: { title: "loves you" },
multi_line: "multi-line strings for the win",
key: null,
path: undefined,
zero: 0,
empty: '',
is: false
};
String.prototype.template = function(data) {
// match 0 or more subpaths
var pattern = /\$\{\s*([\w]+[\?]?(\.[\w]+[\?]?){0,})\s*\}/g;
var resolve = function (p, c) {
// start with 0, not 1 (which assumes 'props.')
// remove ? from keys
var l = p.replace(/\?/g, '').split('.'), i = 0, k;
while (c != null && (k = l[i++])) {
c = c[k];
}
// quiet output if ? found
if (c === undefined && p.indexOf('?') > -1) {
c = '';
}
// don't quiet the output yet
return c// == null ? '' : c;
};
var replace = function (m, p) {
// if data is false-y, replace it with an empty object
var test = resolve(p, data || {});
// don't quiet the output yet
return test === undefined ? m : test;
};
return this.replace(pattern, replace);
}
// health tests
console.log("<h1>${ some.title } ${ another.title}</h1>".template(props));
// => <h1>grooovy loves you</h1>
console.log("\
<h1>${multi_line}</h1>\n\
<p>${ some.title}</p>\n\
<p>${ another.title}!</p>\
".template(props)
);
// => <h1>multi-line strings for the win</h1>
// <p>grooovy</p>
// <p>loves you!</p>"
// pathology tests
console.log("<h1>${ some.title} ${ another.title.toString}</h1>".template(props));
// => <h1>grooovy function toString() {
// [native code]
// }</h1>
console.log("<h1>${oops.no.match} ${ another.title}</h1>".template(props));
// => <h1>${oops.no.match} loves you</h1>
console.log("<h1>${ key.is.null} ${ another.title}</h1>".template(props));
// => <h1>null loves you</h1>
console.log("<h1>${ is.undefined} ${ another.title}</h1>".template(props));
// => <h1>${ is.undefined} loves you</h1>
console.log("<h1>${ zero} ${ another.title}</h1>".template(props));
// => <h1>0 loves you</h1>
console.log("<h1>${ empty} ${ another.title}</h1>".template(props));
// => <h1> loves you</h1>
console.log("<h1>${ is} ${ another.title}</h1>".template(props));
// => <h1>false loves you</h1>
console.warn("<h1>${ path}</h1>".template(props));
// => <h1>${ path}</h1>
console.info('quiet references');
console.log("<h1>1 ${ quiet?}</h1>".template(props));
// => <h1>1 </h1>
console.log("<h1>2 ${ empty.quiet?}</h1>".template(props));
// => <h1>2 </h1>
console.log("<h1>3 ${ is.quiet.undefined? }</h1>".template(props));
// => <h1>3 </h1>
console.log("<h1>3.1 ${ is.quiet.undefined }</h1>".template(props));
// => <h1>3.1 ${ is.quiet.undefined }</h1>
console.log("<h1>4 ${ oops?.hello }</h1>".template(props));
// => <h1>4 </h1>
console.log("<h1>5 ${ oops?.hello }</h1>".template(null));
// => <h1>5 </h1>
console.log("<h1>6 ${ oops?.hello }</h1>".template(undefined));
// => <h1>6 </h1>
// ReferenceError: reference to undefined property "oops"...
console.log(
'<h1 class="${ class }">hello</h1>'.template({ "class": 'wind blown around', })
);
// => <h1 class="wind">hello</h1>
// 23 November 2018 - more prototype pollution
Object.prototype.template = function(string) {
var data = Object.assign(Object.create(null), this);
return string.template(data);
}
console.warn({ whatever: 'yes, whatever'}.template("<h1>${whatever}</h1>"));
// => <h1>yes, whatever</h1>
// 2 DEC 2016
// support an hyperx or hx-like version
// todo - evaluate function calls in literals - ugh
var hx = (function() {
var pattern = /\$\{\s*(props(\.[\w]+)+)\s*\}/g
var resolve = function (p, c) {
var l = p.split('.'), i = 1, k
while (c && (k = l[i++])) { c = c[k] }
return c
};
var replace = function (m, p) {
return resolve(p, props)
};
function template(str) {
str = '' + str;
function render(props) {
return render.template.replace(pattern, function replace(m, p) {
return resolve(p, props)
})
}
render.template = str
render.toString = function(props) {
return render(props)
}
render.valueOf = function() {
return render.template
}
return render
}
return template
}());
/* api test */
// template, tests..
var test = hx("<h1>${ props.some.title }, ${props.another.title}</h1>");
console.log(test)
// Object { render=function(), toString=function(), valueOf=function()}
console.log('' + test)
// <h1>${ props.some.title }, ${props.another.title}</h1>
console.info(
test.toString({
some: {title: "hello"},
another: {title: "templates"}
})
);
// <h1>hello, templates</h1>
/* example data */
var props = {
some: { title: 'grooovy' },
another: { title: "loves you" },
multi_line: "multi-line strings for the win",
key: null,
path: undefined,
zero: 0,
empty: '',
is: false
};
/* health tests */
console.info(hx("<h1>${ props.some.title } ${props.another.title}</h1>")(props));
// => <h1>grooovy loves you</h1>
console.log(hx("\
<h1>${props.multi_line}</h1>\n\
<p>${props.some.title}</p>\n\
<p>${props.another.title}!</p>\
")(props)
);
// => <h1>multi-line strings for the win</h1>
// <p>grooovy</p>
// <p>loves you!</p>"
/* pathology tests */
console.log(hx("<h1>${props.some.title} ${props.another.title.toString}</h1>")(props));
// => <h1>grooovy function toString() {
// [native code]
// }</h1>
console.log(hx("<h1>${oops.no.match} ${props.another.title}</h1>")(props));
// => <h1>{oops.no.match} loves you</h1>
console.log(hx("<h1>${props.key.is.null} ${props.another.title}</h1>")(props));
// => <h1>null loves you</h1>
console.log(hx("<h1>${props.path.is.undefined} ${props.another.title}</h1>")(props));
// => <h1>undefined loves you</h1>
console.log(hx("<h1>${props.zero} ${props.another.title}</h1>")(props));
// => <h1>0 loves you</h1>
console.log(hx("<h1>${props.empty} ${props.another.title}</h1>")(props));
// => <h1> loves you</h1>
console.log('' + hx("<h1>${props.is} ${props.another.title}</h1>")(props));
// => <h1>false loves you</h1>
/* redux-es5 */
var react = {
createElement: function (element) {
return '<' + element.type + '>';
}
};
var AddTodo = { type: 'AddTodo' }
var VisibleTodoList = { type: 'VisibleTodoList' }
var Footer = { type: 'Footer' }
var reactExample = {
todo: react.createElement(AddTodo),
list: react.createElement(VisibleTodoList),
footer: react.createElement(Footer)
}
console.info(reactExample)
console.warn(hx("<div>\
${props.todo}\
${props.list}\
Show ${' '} \
${props.footer}\
</div>")(reactExample))
// <div> created element: AddTodo created element: VisibleTodoList Show ${' '} created element: Footer</div>
// 19 Dec 2016
var props = (function() {
var a = 'gravy', b = 'leves you';
return {
some: { title: function() { return a } },
another: { title: function() { return b } }
}
}());
var pattern = /\$\{\s*(props(\.[\w]+)+)(\(\))?\s*\}/g;
var resolve = function (p, c, f) {
var l = p.split('.'), i = 1, k;
while (c && (k = l[i++])) { c = c[k]; }
if (f != null && typeof c == 'function') {
return c()
}
return c;
};
var replace = function (m, p, c, f) {
return resolve(p, props, f);
};
/* health tests */
console.log("<h1>${ props.some.title() } ${props.another.title()}</h1>".replace(pattern, replace));
// => <h1>gravy leves you</h1>
/// alternate with Function() and some preprocess cheat methods ///
// 27-30 Dec 2016
function template(string) {
var s = ''+string;
var t = Object.create(template.prototype);
return {
render: function(props) {
return t.render(s, props)
}
}
}
template.prototype.pattern = /\$\{\s*(props(\.[\w]+)+)(?:\(([^\)]*)?\))?\s*\}/g;
template.prototype.resolve = function (p, c, a) {
var f = Function('return ' + a), // <= cheating
l = p.split('.'), i = 1, k;
while (c && (k = l[i++])) { c = c[k]; }
return (typeof c == 'function') ? c(f()) : c;
};
template.prototype.render = function(string, props) {
var t = this;
return string.replace(t.pattern, function (m, p, c, a) {
return t.resolve(p, props, a)
});
};
/* health tests */
var data = [777, 444, 222];
var aGlobal = 'this is a global value';
var mapper = function(v) { return "<li>".concat(v, "</li>") };
var string = "\
<h1>${ props.some.title() } \
${ props.another.title }</h1>\
<h2>${ props.global(aGlobal) }</h2>\
<ul>${ props.array(data) }</ul>\
";
var o = (function(a, b) {
a = 'graevy', b = 'loeurves you';
return {
array: function(a) { return a.map(mapper).join('') }, // <= cheating
global: function(g) { return g }, // <= cheating
some: { title: function() { return a } },
another: { title: function() { return b } }
}
}());
template(string).render(o)
// <h1>graevy loeurves you</h1>
// <h2>this is a global value</h2>
// <ul><li>777</li><li>444</li><li>222</li></ul>
// 27 Dec 2016
function template(s) {
var string = ''+s;
var pattern = /\$\{\s*(props(\.[\w]+)+)(\(\))?\s*\}/g;
var resolve = function (p, c, f) {
var l = p.split('.'), i = 1, k;
while (c && (k = l[i++])) { c = c[k]; }
if (f != null && typeof c == 'function') {
return c()
}
return c;
};
var replace = function (m, p, c, f) {
return resolve(p, props, f);
};
return {
replace: function(props) {
return string.replace(pattern, replace)
}
}
}
/* health tests */
var string = "<h1>${ props.some.title() } ${props.another.title() }</h1>\
";
var props = (function() {
var a = 'gravy', b = 'leves you';
return {
some: { title: function() { return a } },
another: { title: function() { return b } }
}
}());
template(string).replace(props)
// => <h1>gravy leves you</h1>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment