// 🌀 Array.prototype + .last
Object.assign(Array.prototype, {
get last() { return this[this.length - 1] }
});
// -----------------------
// ⭐️ tag function
// -----------------------
// `strings` and interpolated `values` are provided by the template literal
function template(strings, ...values) {
// `args` are provided by the function parameters
return (function (...args) {
// last argument is expected to be an "options" objerct
let opts = args.last || {}; // 🌀 Array.prototype + .last
// reconstruct the "template" string (1/3)
let result = [strings[0]];
// for each interpolated value
values.forEach((value, i) => {
// if the value is number, choose from arguments,
// otherwise, choose from the "options" object
let selected = Number.isInteger(value) ? args[value] : opts[value];
// reconstruct the "template" string (2/3)
result.push(selected, strings[i + 1]);
});
// reconstruct the "template" string (3/3)
return result.join('');
});
}
// -------------------------
// ⭐️ tagged templates
// -------------------------
template`${0}${1}${0}!`('Y', 'A'), // "YAY!"
// strings: ['', '', '', '!']
// values: [0, 1, 0] <----------- ⭐️ choose from `args`
// args: ['Y', 'A']
// opts: 'A' (ignored)
template`${0} ${'foo'}!`('Hello', { foo: 'World' }), // "Hello World!"
// strings: ['', ' ', '!']
// values: [0, 'foo'] <-------------- ⭐️ choose from `args` & `opts`
// args: ['Hello', { foo: 'World' }]
// opts: { foo: 'World' }
// ╭s0╮ ╭─── s1 ────╮ ╭── s2 ───╮ <-- strings
template`I'm ${'name'}. I'm almost ${'age'} years old.`({ name: 'MDN', age: 30 }),
// ╰─ v0 ──╯ ╰─ v1 ─╯ <------- interpolated values
// result: "I'm MDN. I'm almost 30 years old."
// ---------------------------------------------
// strings: ["I'm ", ". I'm almost ", " years old."]
// values: ['name', 'age'] <----------- ⭐️ choose from `opts`
// args: [{ name: 'MDN', age: 30 }] (ignored)
// opts: { name: 'MDN', age: 30 }