Стройная ставка многострочных шаблонных литералов в код на JavaScript

от автора


Описание проблемы

Появившиеся в ES6 шаблонные литералы (или шаблонные строки — template literals, template strings) помимо долгожданной интерполяции переменных и выражений принесли возможность вставки многострочного текста без дополнительных ухищрений, усложняющих вид кода.

Однако то, что красиво смотрится в разнообразных примерах на эту тему, в реальном коде порой облекается в новый вид безобразия.

Впрочем, проблемы видны, даже если присмотреться к примерам. Возьмём замечательную статью об этом нововведении из известной серии «ES6 In Depth».

Видите досадные «оспинки»? Лёгкие перекосы в симметрии и стройности?

Маленький пример

var text = ( `foo bar baz`) 

Большой пример

var html = `<article>   <header>     <h1>${title}</h1>   </header>   <section>     <div>${teaser}</div>     <div>${body}</div>   </section>   <footer>     <ul>       ${tags.map(tag => `<li>${tag}</li>`).join('\n      ')}     </ul>   </footer> </article>` 

Возьмём теперь какой-нибудь новый простой пример и посмотрим на проблемы внимательнее.

const a = 1; const b = 2;  console.log( `a = ${a}. b = ${b}.` ); 

1. Первая кавычка искажает стройность текста, портит выравнивание строк.
2. Автоматически кажется, будто она попадёт в вывод, из-за сложной смеси элементов литерала и кода. Приходится дополнительно абстрагироваться от неё, чтобы представить, как будет выглядеть окончательный вывод.
3. Строчки литерала оказываются вровень с вызовом функции, нарушается привычная структура отступов.

Можно сделать так:

const a = 1; const b = 2;  console.log(`   a = ${a}.   b = ${b}. `); 

Это решает упомянутые проблемы: выравнивание строчек сохраняется, элементы кода и литерала разнесены, привычная структура отступов улучшает читабельность, ярче разделяет функцию и аргументы.

Но теперь у нас появляются лишние переводы строк и пробелы. Иногда с этим можно смириться, но на универсальное решение не тянет.

Усугубим наш пример введением дополнительных блоков и отступов.

const verbose = true;  if (verbose) {   console.log( `const a is ${a}. const b is ${b}.`   ); } else {   console.log( `a = ${a}. b = ${b}.`   ); } 

Ужасно. Теперь литерал вообще выпирает слева, разрушая структуру блоков.

Можно исправить описанным выше способом:

if (verbose) {   console.log(`     const a is ${a}.     const b is ${b}.   `); } else {   console.log(`     a = ${a}.     b = ${b}.   `); } 

Стало ещё больше «служебных» пробелов. А если придётся вставлять литерал на ещё более глубоком уровне вложенности? Всё это быстро выйдет из-под контроля.

Присваивания переменным или вызовы console.log можно заменить на функции записи в файлы, дилемма останется той же — или нечитабельная каша, или лишние пробелы и переводы строк:

fs.writeFileSync('log.txt', `a = ${a}. b = ${b}.`, 'ascii'); 

или

fs.writeFileSync('log.txt', `   a = ${a}.   b = ${b}. `, 'ascii'); 

Я нашёл для себя выход, которым решил поделиться. Не столько потому, что рискнул посчитать его общеполезным, сколько для того, чтобы затеять обсуждение: вполне вероятно, что уже найдены другие выходы, и все желающие смогут их распространить.

Возможное решение

Оно кроется в области того же самого нововведения, а именно в функционале под названием «tagged templates». В уже упомянутой статье есть раздел, посвящённый этому механизму и «разжёвывающий» алгоритм его работы до значительной наглядности: «Demystifying Tagged Templates».

Приведённые автором «костяки» функций, обрабатывающих шаблонные литералы, натолкнули меня на мысль использовать нечто подобное и для удаления всех служебных пробелов и переводов строки из многострочных литералов. Получилась такая функция:

//remove auxiliary code spaces in template strings  function xs(strings, ...expressions) {   if (!expressions.length) {     return strings[0].replace(/^ +/mg, '').replace(/^\n|\n$/g, '');   } else {     return strings.reduce((acc, str, i) => {       return (         (i === 1? acc.replace(/^ +/mg, '') : acc) +         expressions[i - 1] +         str.replace(/^ +/mg, '')       );     }).replace(/^\n|\n$/g, '');   } } 

Или вариант, пригодный для Node.js на то время, пока rest parameters остаются под флагом:

//remove auxiliary code spaces in template strings  function xs(strings) {   const expressions = Array.from(arguments).slice(1);    if (!expressions.length) {     return strings[0].replace(/^ +/mg, '').replace(/^\n|\n$/g, '');   } else {     return strings.reduce((acc, str, i) => {       return (         (i === 1? acc.replace(/^ +/mg, '') : acc) +         expressions[i - 1] +         str.replace(/^ +/mg, '')       );     }).replace(/^\n|\n$/g, '');   } } 

Как можно заметить, функция убирает по одному начальному и конечному переводу строки из конечного результата, а также удаляет все начальные пробелы в строчках (не затрагивая интерполируемые переменные и выражения).

Теперь можно смело использовать наши более читабельные варианты, с небольшим, еле заметным добавлением, которое не портит вид кода (впрочем, функцию можно назвать как угодно — длиннее, короче, используя разные варианты наглядности, интуитивной понятности и т.д.):

const a = 1; const b = 2;  console.log(xs`   a = ${a}.   b = ${b}. `);  const verbose = true;  if (verbose) {   console.log(xs`     const a is ${a}.     const b is ${b}.   `); } else {   console.log(xs`     a = ${a}.     b = ${b}.   `); } 

Надеюсь, это только начальная цена лота, и появятся другие идеи, отличающиеся более или менее радикально от предложенной. Или же будут найдены неочевидные препятствия к использованию подобных решений.

ссылка на оригинал статьи https://habrahabr.ru/post/280928/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *