Last active
November 23, 2018 20:50
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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> | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /// 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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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