├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── cjs ├── index.js └── package.json ├── es.js ├── esm └── index.js ├── index.js ├── min.js ├── package.json ├── rollup ├── babel.config.js └── es.config.js └── test ├── index.html ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .nyc_output 3 | node_modules/ 4 | package-lock.json 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .nyc_output 3 | .travis.yml 4 | node_modules/ 5 | rollup/ 6 | test/ 7 | package-lock.json 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - stable 4 | git: 5 | depth: 1 6 | branches: 7 | only: 8 | - master 9 | after_success: 10 | - "npm run coveralls" 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2020, Andrea Giammarchi, @WebReflection 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 14 | OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tag-params 2 | 3 | [![Build Status](https://travis-ci.com/WebReflection/tag-params.svg?branch=master)](https://travis-ci.com/WebReflection/tag-params) [![Coverage Status](https://coveralls.io/repos/github/WebReflection/tag-params/badge.svg?branch=master)](https://coveralls.io/github/WebReflection/tag-params?branch=master) 4 | 5 | Transform a generic string into parameters suitable for template literals functions tags. 6 | 7 | ```js 8 | import {params} from 'tag-params'; 9 | // const {params} = require('tag-params'); 10 | // 11 | 12 | console.log( 13 | params('Random: ${Math.random()}!') 14 | ); 15 | // [["Random: ", "!"], 0.3456787643] 16 | 17 | console.log( 18 | params('Hello ${user}', {user: 'test'}) 19 | ); 20 | // [["Hello ", ""], "test"] 21 | 22 | // invoke tags through the returned parameters 23 | genericTag(...params(content, namespace)); 24 | ``` 25 | 26 | 27 | ## API 28 | 29 | There are 3 utilities exported by this module, so that accordingly with your import, you should get: 30 | 31 | * `params`, the main utility, which parses and resolves _values_ in one go. 32 | * `parse`, which returns a `{template, values}` object, with mapped "_chunks_" in the template, and the list of _values_ (interpolations/holes), where each value is a string. 33 | * `partial`, which uses `parse` and returns a callback that will map new values through the optional `object` passed along. 34 | 35 | 36 | ### params(content:string, object?:any) => [string[], ...any[]] 37 | 38 | It's the "_default_" use case of this utility. It parses the `content` and returns a `[template, ...values]` _Array_ with values retrieved through the optional `object`. If no `object` is passed along, it simply evaluates interpolations as plain JavaScript. 39 | 40 | This utility is a shortcut for a one-off `partial(content)(object)` call. 41 | 42 | 43 | ### parse(content:string, transform?:function) => {template:string[], values:string[]} 44 | 45 | It parses a string, and it uses the optional `transform` callback, which is _no-op_ as default, to assign each _value_ to the list of expected _values_. 46 | 47 | The `transform` optional callback is specially useful when the interpolated content might contains _HTML_ normalized chars, such as `value => stuff(value)` instead of `value => stuff(value)`, which is normal when the content is retrieved via `element.innerHTML`, as example. 48 | 49 | The `template` property contains all chunks around `${...}` interpolations, while `values` contains all interpolations content as string. 50 | 51 | 52 | ### partial(content:string, transform?:function) => (object?) => [string[], ...any[]] 53 | 54 | This utility parses the `content` through an optional `transform`, and returns a _callback_ that accepts a new object each time. 55 | 56 | This is particularly useful to avoid parsing the same template content over and over, and just update its interpolation _values_ through the optional `object`. 57 | 58 | ```js 59 | import {partial} from 'tag-params'; 60 | const update = partial('Hello ${user}!'); 61 | 62 | console.log(update({user: 'First'})); 63 | // [["Hello ", "!"], "First"] 64 | 65 | console.log(update({user: 'Second'})); 66 | // [["Hello ", "!"], "Second"] 67 | 68 | // always true 69 | console.assert( 70 | update({user: 'First'})[0] === 71 | update({user: 'Second'})[0] 72 | ); 73 | ``` 74 | 75 | The main advantage of this utility is that it parses the `content` and it creates the `template` _Array_ only **once**, meaning every template literal based library could benefit from it, using the uniqueness of the `template` to parse complex chunks of _HTML_, or anything else, once, enabling repeated updates at almost zero performance cost. 76 | 77 | 78 | 79 | ## Use Cases 80 | 81 | The most common/requested use case for this is likely landing templates on the page and use their content, [as shown in this CodePen example](https://codepen.io/WebReflection/pen/OJMRZow?editors=0010): 82 | 83 | ```html 84 | 89 |
90 | 91 | 106 | ``` 107 | 108 | This works well with libraries such as [uhtml](https://github.com/WebReflection/uhtml#readme), [lighterhtml](https://github.com/WebReflection/lighterhtml#readme), or [hyperHTML](https://github.com/WebReflection/hyperHTML#readme), as well as any library based on template literals tags. 109 | 110 | However, this module can work with literally any possible template literal tag function, as these all share the same signature, hence will accept transformed chunks, as _template_, and the rest of the interpolations as _values_. 111 | 112 | 113 | ## Caveats 114 | 115 | Please note this module inevitably needs/uses `Function` to evaluate the code within a `with` statement, as there's no other way to evaluate interpolations through passed data. 116 | 117 | Moreover, the current interpolations parser is extremely rudimental, it simply skips extra `{` and `}` chars within the value, but it doesn't parse all possible JS syntax. 118 | 119 | This means that if an interpolation contains a string such as `${"breaking { char"}` or `${"breaking } char"}` the result will break. 120 | 121 | The good practice here is to pass strings via the `object`, instead of hard coding these within interpolations, as this won't likely get fixed any time soon (if ever). 122 | 123 | 124 | 125 | ## Compatibility 126 | 127 | Every JavaScript engine, either client, server, or IoT, that supports `string[index]` access, as I couldn't bother myself adding a slow and jurassic `string.charAt(index)` in the code. 128 | -------------------------------------------------------------------------------- /cjs/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @typedef {object} ParseResult an object with parsed results 4 | * @property {string[]} template - the list of chunks around interpolations 5 | * @property {string[]} values - interpolations as strings 6 | */ 7 | 8 | /** 9 | * @typedef {[string[], ...any[]]} TagArguments an array to use as template 10 | * literals tag arguments 11 | */ 12 | 13 | /** 14 | * @callback Partial a callback that re-evaluate each time new data associated 15 | * to the same template-like array. 16 | * @param {object} [object] the optional data to evaluate as interpolated values 17 | * @returns {TagArguments} an array to use as template literals tag arguments 18 | */ 19 | 20 | /** 21 | * The default `transform` callback 22 | * @param {string} value the interpolation value as string 23 | */ 24 | const noop = value => value; 25 | 26 | /** 27 | * The default "null" fallback when no object is passed to the Partial. 28 | */ 29 | const fallback = Object.create(null); 30 | 31 | /** 32 | * Given a string and an optional object carrying references used through 33 | * such string interpolation, returns an array that can be used within any 34 | * template literal function tag. 35 | * @param {string} content the string to parse/convert as template chunks 36 | * @param {object} [object] the optional data to evaluate as interpolated values 37 | * @returns {TagArguments} an array to use as template literals tag arguments 38 | */ 39 | const params = (content, object) => partial(content)(object); 40 | exports.params = params; 41 | 42 | /** 43 | * Given a string and an optional function used to transform each value 44 | * found as interpolated content, returns an object with a `template` and 45 | * a `values` properties, as arrays, containing the template chunks, 46 | * and all its interpolations as strings. 47 | * @param {string} content the string to parse/convert as template chunks 48 | * @param {function} [transform] the optional function to modify string values 49 | * @returns {ParseResult} an object with `template` and `values` arrays. 50 | */ 51 | const parse = (content, transform) => { 52 | const fn = transform || noop; 53 | const template = []; 54 | const values = []; 55 | const {length} = content; 56 | let i = 0; 57 | while (i <= length) { 58 | let open = content.indexOf('${', i); 59 | if (-1 < open) { 60 | template.push(content.slice(i, open)); 61 | open = (i = open + 2); 62 | let close = 1; 63 | // TODO: this *might* break if the interpolation has strings 64 | // containing random `{}` chars ... but implementing 65 | // a whole JS parser here doesn't seem worth it 66 | // for such irrelevant edge-case ... or does it? 67 | while (0 < close && i < length) { 68 | const c = content[i++]; 69 | close += c === '{' ? 1 : (c === '}' ? -1 : 0); 70 | } 71 | values.push(fn(content.slice(open, i - 1))); 72 | } 73 | else { 74 | template.push(content.slice(i)); 75 | i = length + 1; 76 | } 77 | } 78 | return {template, values}; 79 | }; 80 | exports.parse = parse; 81 | 82 | /** 83 | * Given a string and an optional function used to transform each value 84 | * found as interpolated content, returns a callback that can be used to 85 | * repeatedly generate new content from the same template array. 86 | * @param {string} content the string to parse/convert as template chunks 87 | * @param {function} [transform] the optional function to modify string values 88 | * @returns {Partial} a function that accepts an optional object to generate 89 | * new content, through the same template, each time. 90 | */ 91 | const partial = (content, transform) => { 92 | const {template, values} = parse(content, transform); 93 | const args = [template]; 94 | const rest = Function( 95 | 'return function(){with(arguments[0])return[' + values + ']}' 96 | )(); 97 | return object => args.concat(rest(object || fallback)); 98 | }; 99 | exports.partial = partial; 100 | -------------------------------------------------------------------------------- /cjs/package.json: -------------------------------------------------------------------------------- 1 | {"type":"commonjs"} -------------------------------------------------------------------------------- /es.js: -------------------------------------------------------------------------------- 1 | self.tagParams=function(t){"use strict";const e=t=>t,n=Object.create(null),r=(t,n)=>{const r=n||e,s=[],a=[],{length:u}=t;let c=0;for(;c<=u;){let e=t.indexOf("${",c);if(-1{const{template:s,values:a}=r(t,e),u=[s],c=Function("return function(){with(arguments[0])return["+a+"]}")();return t=>u.concat(c(t||n))};return t.params=(t,e)=>s(t)(e),t.parse=r,t.partial=s,t}({}); 2 | -------------------------------------------------------------------------------- /esm/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {object} ParseResult an object with parsed results 3 | * @property {string[]} template - the list of chunks around interpolations 4 | * @property {string[]} values - interpolations as strings 5 | */ 6 | 7 | /** 8 | * @typedef {[string[], ...any[]]} TagArguments an array to use as template 9 | * literals tag arguments 10 | */ 11 | 12 | /** 13 | * @callback Partial a callback that re-evaluate each time new data associated 14 | * to the same template-like array. 15 | * @param {object} [object] the optional data to evaluate as interpolated values 16 | * @returns {TagArguments} an array to use as template literals tag arguments 17 | */ 18 | 19 | /** 20 | * The default `transform` callback 21 | * @param {string} value the interpolation value as string 22 | */ 23 | const noop = value => value; 24 | 25 | /** 26 | * The default "null" fallback when no object is passed to the Partial. 27 | */ 28 | const fallback = Object.create(null); 29 | 30 | /** 31 | * Given a string and an optional object carrying references used through 32 | * such string interpolation, returns an array that can be used within any 33 | * template literal function tag. 34 | * @param {string} content the string to parse/convert as template chunks 35 | * @param {object} [object] the optional data to evaluate as interpolated values 36 | * @returns {TagArguments} an array to use as template literals tag arguments 37 | */ 38 | export const params = (content, object) => partial(content)(object); 39 | 40 | /** 41 | * Given a string and an optional function used to transform each value 42 | * found as interpolated content, returns an object with a `template` and 43 | * a `values` properties, as arrays, containing the template chunks, 44 | * and all its interpolations as strings. 45 | * @param {string} content the string to parse/convert as template chunks 46 | * @param {function} [transform] the optional function to modify string values 47 | * @returns {ParseResult} an object with `template` and `values` arrays. 48 | */ 49 | export const parse = (content, transform) => { 50 | const fn = transform || noop; 51 | const template = []; 52 | const values = []; 53 | const {length} = content; 54 | let i = 0; 55 | while (i <= length) { 56 | let open = content.indexOf('${', i); 57 | if (-1 < open) { 58 | template.push(content.slice(i, open)); 59 | open = (i = open + 2); 60 | let close = 1; 61 | // TODO: this *might* break if the interpolation has strings 62 | // containing random `{}` chars ... but implementing 63 | // a whole JS parser here doesn't seem worth it 64 | // for such irrelevant edge-case ... or does it? 65 | while (0 < close && i < length) { 66 | const c = content[i++]; 67 | close += c === '{' ? 1 : (c === '}' ? -1 : 0); 68 | } 69 | values.push(fn(content.slice(open, i - 1))); 70 | } 71 | else { 72 | template.push(content.slice(i)); 73 | i = length + 1; 74 | } 75 | } 76 | return {template, values}; 77 | }; 78 | 79 | /** 80 | * Given a string and an optional function used to transform each value 81 | * found as interpolated content, returns a callback that can be used to 82 | * repeatedly generate new content from the same template array. 83 | * @param {string} content the string to parse/convert as template chunks 84 | * @param {function} [transform] the optional function to modify string values 85 | * @returns {Partial} a function that accepts an optional object to generate 86 | * new content, through the same template, each time. 87 | */ 88 | export const partial = (content, transform) => { 89 | const {template, values} = parse(content, transform); 90 | const args = [template]; 91 | const rest = Function( 92 | 'return function(){with(arguments[0])return[' + values + ']}' 93 | )(); 94 | return object => args.concat(rest(object || fallback)); 95 | }; 96 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | self.tagParams = (function (exports) { 2 | 'use strict'; 3 | 4 | /** 5 | * @typedef {object} ParseResult an object with parsed results 6 | * @property {string[]} template - the list of chunks around interpolations 7 | * @property {string[]} values - interpolations as strings 8 | */ 9 | 10 | /** 11 | * @typedef {[string[], ...any[]]} TagArguments an array to use as template 12 | * literals tag arguments 13 | */ 14 | 15 | /** 16 | * @callback Partial a callback that re-evaluate each time new data associated 17 | * to the same template-like array. 18 | * @param {object} [object] the optional data to evaluate as interpolated values 19 | * @returns {TagArguments} an array to use as template literals tag arguments 20 | */ 21 | 22 | /** 23 | * The default `transform` callback 24 | * @param {string} value the interpolation value as string 25 | */ 26 | var noop = function noop(value) { 27 | return value; 28 | }; 29 | /** 30 | * The default "null" fallback when no object is passed to the Partial. 31 | */ 32 | 33 | 34 | var fallback = Object.create(null); 35 | /** 36 | * Given a string and an optional object carrying references used through 37 | * such string interpolation, returns an array that can be used within any 38 | * template literal function tag. 39 | * @param {string} content the string to parse/convert as template chunks 40 | * @param {object} [object] the optional data to evaluate as interpolated values 41 | * @returns {TagArguments} an array to use as template literals tag arguments 42 | */ 43 | 44 | var params = function params(content, object) { 45 | return partial(content)(object); 46 | }; 47 | /** 48 | * Given a string and an optional function used to transform each value 49 | * found as interpolated content, returns an object with a `template` and 50 | * a `values` properties, as arrays, containing the template chunks, 51 | * and all its interpolations as strings. 52 | * @param {string} content the string to parse/convert as template chunks 53 | * @param {function} [transform] the optional function to modify string values 54 | * @returns {ParseResult} an object with `template` and `values` arrays. 55 | */ 56 | 57 | var parse = function parse(content, transform) { 58 | var fn = transform || noop; 59 | var template = []; 60 | var values = []; 61 | var length = content.length; 62 | var i = 0; 63 | 64 | while (i <= length) { 65 | var open = content.indexOf('${', i); 66 | 67 | if (-1 < open) { 68 | template.push(content.slice(i, open)); 69 | open = i = open + 2; 70 | var close = 1; // TODO: this *might* break if the interpolation has strings 71 | // containing random `{}` chars ... but implementing 72 | // a whole JS parser here doesn't seem worth it 73 | // for such irrelevant edge-case ... or does it? 74 | 75 | while (0 < close && i < length) { 76 | var c = content[i++]; 77 | close += c === '{' ? 1 : c === '}' ? -1 : 0; 78 | } 79 | 80 | values.push(fn(content.slice(open, i - 1))); 81 | } else { 82 | template.push(content.slice(i)); 83 | i = length + 1; 84 | } 85 | } 86 | 87 | return { 88 | template: template, 89 | values: values 90 | }; 91 | }; 92 | /** 93 | * Given a string and an optional function used to transform each value 94 | * found as interpolated content, returns a callback that can be used to 95 | * repeatedly generate new content from the same template array. 96 | * @param {string} content the string to parse/convert as template chunks 97 | * @param {function} [transform] the optional function to modify string values 98 | * @returns {Partial} a function that accepts an optional object to generate 99 | * new content, through the same template, each time. 100 | */ 101 | 102 | var partial = function partial(content, transform) { 103 | var _parse = parse(content, transform), 104 | template = _parse.template, 105 | values = _parse.values; 106 | 107 | var args = [template]; 108 | var rest = Function('return function(){with(arguments[0])return[' + values + ']}')(); 109 | return function (object) { 110 | return args.concat(rest(object || fallback)); 111 | }; 112 | }; 113 | 114 | exports.params = params; 115 | exports.parse = parse; 116 | exports.partial = partial; 117 | 118 | return exports; 119 | 120 | }({})); 121 | -------------------------------------------------------------------------------- /min.js: -------------------------------------------------------------------------------- 1 | self.tagParams=function(r){"use strict";var t=function(r){return r},n=Object.create(null),e=function(r,n){for(var e=n||t,u=[],a=[],c=r.length,i=0;i<=c;){var s=r.indexOf("${",i);if(-1 2 | 3 | 4 | 5 | 6 | tag-params 7 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {params, partial} = require('../cjs'); 4 | 5 | const tag = (template, ...values) => { 6 | const out = [template[0]]; 7 | for (let i = 0, {length} = values; i < length; i++) 8 | out.push(values[i], template[i + 1]); 9 | return out.join(''); 10 | }; 11 | 12 | const expect = (params, length, outcome, message) => { 13 | console.assert( 14 | params[0].length === length && 15 | tag(...params) === outcome, 16 | message 17 | ); 18 | }; 19 | 20 | expect(params(''), 1, '', 'no interpolations is OK'); 21 | expect(params('${}'), 2, '', 'empty around interpolations is OK'); 22 | expect(params('${123}'), 2, '123', 'single interpolation is OK'); 23 | expect(params('a${"b"}c'), 2, 'abc', 'single interpolation in between is OK'); 24 | expect(params('${"a"}b${"c"}'), 3, 'abc', 'multi interpolations are OK'); 25 | expect(params('${"a"}${"b"}${"c"}'), 4, 'abc', 'only interpolations are OK'); 26 | expect(params('Hello ${user}', {user: 'test'}), 2, 'Hello test', 'namespace is OK'); 27 | expect(params('Hello ${function (){}}'), 2, 'Hello function (){}', 'inner curly is OK'); 28 | expect(params('${[1,2,3]}'), 2, '1,2,3', 'array is OK'); 29 | expect(partial('a${123}b', () => 456)(), 2, 'a456b', 'partial is OK'); 30 | 31 | const update = partial('Hello ${user}!'); 32 | console.assert( 33 | update({user: 'First'})[0] === 34 | update({user: 'Second'})[0], 35 | 'partial returns always the same template' 36 | ); 37 | 38 | setTimeout(stringify => { 39 | const length = 500; 40 | const tpl = 'Hello ${user}!'; 41 | 42 | const partialResult = []; 43 | console.time('partial'); 44 | const update = partial(tpl); 45 | for (let user = 0; user < length; user++) 46 | partialResult.push(update({user})); 47 | console.timeEnd('partial'); 48 | 49 | const paramsResult = []; 50 | console.time('params'); 51 | for (let user = 0; user < length; user++) 52 | paramsResult.push(params(tpl, {user})); 53 | console.timeEnd('params'); 54 | 55 | console.assert( 56 | partialResult.every( 57 | (args, i) => stringify(args) === stringify(paramsResult[i]) 58 | ), 59 | 'same outcome' 60 | ); 61 | }, 500, JSON.stringify); 62 | -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | {"type":"commonjs"} --------------------------------------------------------------------------------