├── .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 | [](https://travis-ci.com/WebReflection/tag-params) [](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 |
85 | ${items.map(function (item) {
86 | return html`- ${item.text}
`;
87 | })}
88 |
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"}
--------------------------------------------------------------------------------