Описание проблемы
Появившиеся в 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/
Добавить комментарий