Clean, easily readable conditional statements in template literals/strings for Node.js and browsers. Provides if
/ elseif
/ else
and switch
/ case
/ default
syntax options.
const html = _`<ul>
${_if(aVariable === 1)}
<li>First</li>
<li>Second</li>
${_elseif(aVariable === 2)}
<li>Third</li>
<li>Fourth</li>
${_else}
<li>Fifth</li>
${_endif}
<li>Last</li>
</ul>`;
- Why?
- ES module
- Installation
- Usage
- Importing convenience
- License
Simple conditional statements in template literals are no problem:
const str = `This is a sentence ${(true) ? 'with' : 'without'} a conditional statement.`;
Things can get messy and hard to read/maintain when you're working with multi-line strings, longer conditional strings and/or more possible conditions and therefore nested literals:
const c = 2;
const str = `Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. ${(c === 1) ? `Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. ` : (c > 2) ? `Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur.` : `Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`} Alea iacta est.`;
This is where one would likely implement other solutions like splitting up the string or writing a function to handle all cases. But this would render the template literal obsolete or would take parts of the string creation to another place. This library tries to solve these problems:
const str = _`Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. ${_if(c === 1)}Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. ${_elseif(c > 2)}Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur.${_else}Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.${_endif} Alea iacta est.`;
conditional-tag has been written as an ECMAScript module and all examples will use ES module syntax. If you want to use conditional-tag within a CommonJS application, use dynamic import()
instead of require()
.
npm install conditional-tag
You'll find a browser-ready, minimized file in the browser
directory: conditional-tag.min.js
.
<script type="module">
import { _, _if, _else, _endif } from './conditional-tag.min.js';
console.log(_`This is working in the browser too! ${_if(true)}Yay${_else}Boo${_endif}!`);
</script>
conditional-tag provides the tag function _()
. If a template string is tagged with this function, all conditional-tag expressions described further below can be used within the string.
import { _ } from 'conditional-tag';
const str = _`This string is tagged and ready for conditional-tag expressions.`;
TypeError
will likely get thrown ("Cannot convert a Symbol value to a string").
conditional-tag provides the following expressions for conditional blocks using if syntax.
Opens an if-block and evaluates the passed condition. If it produces a truthy result, the strings and (non-if-block) expressions following the ${_if(…)}
expression will be rendered until another if-block expression or the end of the template string is encountered.
Evaluates the passed condition if no preceding condition has been met. If the condition produces a truthy result, the strings and (non-if-block) expressions following the ${_elseif(…)}
expression will be rendered until another if-block expression or the end of the template string is encountered.
If no preceding condition has been met, the strings and (non-if-block) expressions following the ${_else}
expression will be rendered until an ${_endif}
expression or the end of the template string is encountered.
Closes the if-block. Can be omitted if ${_endif}
would be at the very end of the template string but it is not recommended, especially with nested conditional-tag blocks.
Note: While _if()
and _elseif()
are functions, _else
and _endif
are variables.
A ConditionalTagSyntaxError
is thrown when there's something wrong with the syntax, like using _elseif(…)
after _else
.
import { _, _if, _elseif, _else, _endif } from 'conditional-tag';
let x = 1;
let str = _`X is ${_if(x === 1)}one${_elseif(x === 2)}two${_else}neither one nor two${_endif}.`;
// Result: 'X is one.'
x = 2;
str = _`X is ${_if(x === 1)}one${_elseif(x === 2)}two${_else}neither one nor two${_endif}.`;
// Result: 'X is two.'
x = 3;
str = _`X is ${_if(x === 1)}one${_elseif(x === 2)}two${_else}neither one nor two${_endif}.`;
// Result: 'X is neither one nor two.'
conditional-tag provides the following expressions for conditional blocks using switch syntax.
switch(…)
statement in JavaScript, there is no fall-through and no break
. Each _case(…)
will be evaluated independently. However, _case(…)
can be passed multiple values, all of which will be compared to the switch-value. The condition is met, when at least one value is equal to it.
Another difference is that _default
must come after the _case()
expressions.
Opens a switch-block. Stores the value for further comparison. Does not affect the directly following strings and (non-switch-block) expressions.
Tests the passed value(s) and the one stored in ${_switch(…)}
for strict equality. If at least one of the values are equal, the strings and (non-switch-block) expressions following the ${_case(…)}
expression will be rendered until another switch-block expression or the end of the template string is encountered.
If there's no string or expression between ${_switch(…)}
and ${_case(…)}
, they can be chained together as one expression: ${_switch(…)._case(…)}
.
If no preceding ${_case(…)}
expression had values equal to the switch, the strings and (non-switch-block) expressions following the ${_default}
expression will be rendered until an ${_endswitch}
expression or the end of the template string is encountered.
Closes the switch-block. Can be omitted if ${_endswitch}
would be at the very end of the template string but it is not recommended, especially with nested conditional-tag blocks.
Note: While _switch()
and _case()
are functions, _default
and _endswitch
are variables.
A ConditionalTagSyntaxError
is thrown when there's something wrong with the syntax, like using _case([…])
without a preceding _switch([…])
.
import { _, _switch, _case, _default, _endswitch } from 'conditional-tag';
let x = 1;
let str = _`${_switch(x)}X is ${_case(1)}one${_case(2)}two${_default}neither one nor two${_endswitch}.`;
// Result: 'X is one.
// _case() with multiple values:
x = 1;
str = _`When x is 1,
${_switch(x)._case(1)}this will be shown.
${_case(1, 2)}This will be shown when x is 1 or 2.
${_case(3)}Shown when x is 3.
${_default}Shown when x is anything but 1, 2 or 3.`;
// Result:
// 'When x is 1,
// this will be shown.
// This will be shown, when x is 1 or 2.'
conditional-tag also provides a special _always
expression. Strings and (non-conditional-tag) expressions following ${_always}
will always be rendered until a conditional-tag expression or the end of the template string is encountered, even if they are within a block which would otherwise stay unrendered (no matter the nesting depth).
import { _, _switch, _case, _default, _endswitch, _always } from 'conditional-tag';
let x = 1;
let str = _`${_switch(x)}X is ${_case(1)}one${_case(2)}two${_always} (This will be rendered anyway!)${_default}neither one nor two${_endswitch}.`;
// Result: 'X is one (This will be rendered anyway!).'
Naturally, you can use non-condtional-tag expressions in tagged template literals.
import { _, _if } from 'conditional-tag';
function test() {
return 'called';
}
const str = _`${_if(false)} not rendered ${test()}`;
// Result: '' but test() has been called despite being in an unrendered block.
To prevent this from happening, just wrap your function call in an arrow function. The tag function will recognize them and only trigger a call in rendered blocks.
import { _, _if } from 'conditional-tag';
function test() {
return 'called';
}
const str = _`${_if(false)} not rendered ${() => test()}`;
// Result: '' and test() will NOT have been called.
This may make the template literal a bit harder to read but it saves precious CPU time.
If you have to call asynchronous functions in an expression, use the special _async()
tag function. It works exactly as the _()
tag function but is able to handle Promises. There's no need to change the wrapping arrow function.
import { _async, _if } from 'conditional-tag';
function asyncTest() {
return Promise.resolve('called');
}
const str = await _async`${_if(true)}${() => asyncTest()}`;
// Result: 'called'
Note: If you use the synchronous tag function _()
and call asynchronous functions in expressions, you will get errors.
Both, if-blocks and switch-blocks can be nested in other if- or switch-blocks:
import { _, _if, _else, _endif, _switch, _case, _endswitch } from 'conditional-tag';
const x = true;
const y = false;
const str = _`
${_switch(x)._case(true)}
x is true.
${_if(y === true)}
y is true as well.
${_else}
y is NOT true as well.
${_endif}
${_case(false)}
x is false.
${_endswitch}
`;
// Result:
// 'x is true.
// y is NOT true as well.'
Be mindful of your ${_endif}
and ${_endswitch}
expressions because forgetting or misplacing them will lead to confusing results and/or errors!
Conditional-tag expressions don't add any whitespace to the resulting string. Furthermore, if a conditional-tag expression is the only expression in a line of a multi-line template string and is only - if at all - surrounded by whitespace characters, the whole line gets trimmed.
import { _, _switch, _case, _default, _endswitch, _always } from 'conditional-tag';
const x = 1;
const html = _`${_switch(x)}
<ul>
<li>First item</li>
<li>Second item</li>
${_case(1)}
<li>Third item</li>
${_case(2)}
<li>Forth item</li>
${_case(3)}
<li>Fifth item</li>
<li>Sixth item</li>
${_always}
</ul>
${_endswitch}`;
/*
Result:
`<ul>
<li>First item</li>
<li>Second item</li>
<li>Third item</li>
</ul>`
*/
All conditional-tag expressions are also available through the tag function itself (without the leading underscore), so they don't have to be imported individually. This could however throw off syntax highlighting in your editor of choice (and also be considered a tad less readable).
import { _ } from 'conditional-tag';
let x = 2;
str = _`X is ${_.if(x === 1)}one${_.elseif(x === 2)}two${_.else}neither one nor two${_.endif}.`;
// Result: 'X is two.'
conditional-tag is © 2024 Walter Krivanek and released under the MIT license.