Wielka ucieczka
W Eleventy pliki .md to nie do końca zwykły Markdown. Eleventy pozwala w nich bowiem mieszać Markdowna ze składnią Liquida. To działa bardzo fajnie… do momentu, w którym nie dochodzi do konfliktów.
Poważne kłótnie
Gdy pisałem artykuł o dygresjach, próbowałem pokazać, jak wygląda składnia nowego rozwiązania. Użyłem wówczas zapisu ze znakami ucieczki:
{\% note %}Treść dygresji{\% endnote %}
Wszystko po to, żeby Eleventy nie potraktował tego jako kodu w składni Liquida i nie wygenerował dygresji.
Myślałem, że w ten prymitywny sposób udało mi się ominąć problem. A potem pojawił się wpis o tworzeniu dyskusji przy pomocy GitHub Actions i okazało się, że tak nie do końca:

Przykład uciętej zmiennej środowiskowej
Ten fragment kodu miał wyglądać następująco:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # 17
GitHub Actions do wstawiania wartości zmiennych używa takiej samej składni co Liquid, czyli wąsów ({{ }}). Zatem Eleventy spróbowało podstawić wartość zmiennej z Liquida o nazwie secrets.GITHUB_TOKEN do treści. Takiej zmiennej jednak nie ma, więc ostatecznie zostało puste miejsce.
Stwierdziłem zatem, że coś trzeba z tym zrobić. W końcu musi być jakiś sposób, żeby ominąć problem nakładających się składni!
Rozwiązanie
Zajrzałem więc do dokumentacji Liquida. Okazało się, że, faktycznie, jest coś, co pozwoli mi rozwiązać mój problem: tag raw. Jeśli otoczy się nim fragment treści, to nie będzie on parsowany przez Liquida. Tym samym – powinien przetrwać w niezmienionej formie i wylądować w wynikowym pliku HTML!
Co jeszcze ciekawsze, okazało się, że… już kiedyś ten tag zastosowałem. A dokładniej: we wpisie o formatowaniu tekstu przy pomocy metody Array#reduce(). Jakoś totalnie o tym zapomniałem. Niemniej, ponownie uzbrojony w tę wiedzę mogłem teraz poprawić problematyczne wpisy!
Tylko że… jestem leniwym i zapominalskim człowiekiem. Raz, że nie chce mi się wszędzie pisać tego tagu raw; dwa – nie jest mi on potrzebny na tyle często, żebym o nim pamiętał. Przy kolejnym tego typu problemie jeszcze raz bym musiał przechodzić cały proces przypominania sobie o tym rozwiązaniu. Wypadałoby to jakoś zautomatyzować!
Dobre rozwiązanie
Na szczęście Eleventy udostępnia preprocesory. Pozwalają one zmodyfikować pliki wpisów, zanim trafią one do parserów Liquida i Markdowna. Jest więc to idealny moment, żeby zaaplikować tag raw w odpowiednich miejscach!
Tak wygląda mój preprocesor:
const codeBlockRegex = /^```(?:.+)?\n(?:.|\n)+?\n```\n/gmu; // 4
export function createLiquidPreprocessor( eleventyConfig ) { // 1
eleventyConfig.addPreprocessor( 'liquidPreprocessor', 'md', ( _, content ) => { // 2
return content.replaceAll( codeBlockRegex, '{\% raw %}$&{\% endraw %}' ); // 3
} );
}
Dygresja
Cóż, tag raw to ten jeden, w którym trzeba stosować znaki ucieczki – tego już się raczej nie da obejść…
Tworzymy funkcję createLiquidPreprocessor() (1), którą eksportujemy. Ta funkcja jako argument przyjmuje obiekt konfiguracyjny Eleventy. Wywołujemy jego metodę #addPreprocessor() (2) i przekazujemy mu nasz preprocesor. Podajemy jego nazwę (liquidPreprocessor) oraz rozszerzenie pliku, dla którego ma się odpalać (md). Sam preprocesor przekazujemy jako callback. Jego drugi argument to treść pliku z wpisem. Z callbacku zwracamy treść z podmienionymi wszystkimi blokami kodu na takie otoczone tagiem raw (3). Bloki kodu wykrywamy przy pomocy wyrażenia regularnego (4):
^```oznacza linię, która zaczyna się od potrójnego grawisa (`),(?:.+)?oznacza nazwę języka, która może składać się z dowolnej liczby dowolnych znaków (.+) oraz może wystąpić raz lub wcale (?),\noznacza nową linię, następującą po grawisach i opcjonalnej nazwie języka,(?:.|\n)+?oznacza zawartość bloczka kodu, która może składać się z dowolnych znaków oraz nowych linii,\n```\noznacza zakończenie bloczka, czyli potrójny grawis otoczony znakami nowej linii (inaczej mówiąc: linia, w której znajdują się wyłącznie trzy grawisy).
Natomiast samą podmianę robimy przy pomocy wzorca '{% raw %}$&{\% endraw %}'. Metoda #replaceAll() pozwala używać specjalnych wyrażeń, które dają dostęp do rzeczy znalezionych przez wyrażenie regularne. W naszym wypadku używamy wyrażenia $& oznaczającego cały znaleziony ciąg.
Ostatnim etapem jest zaimportowanie funkcji createLiquidPreprocessor() w pliku eleventyConfig.js i wywołanie jej. Od teraz wszystkie bloczki kodu w plikach .md będą automatycznie otaczane przy pomocy tagów raw!
To rozwiązanie ma jedną wadę: nie pozwala używać podstawiania zmiennych Liquidowych wewnątrz bloczków kodu:
const zmienna = '{{ zmiennaZLiquida }}';
Zamiast wartości zmiennej, powyższy kod wyświetli po prostu {{ zmiennaZLiquida }}. Niemniej od momentu powstania bloga ani razu tego nie potrzebowałem, więc… brzmi jak coś, co mogę odłożyć na później.
Tak oto niniejszy blog ulepszył się o kolejną, małą rzecz!
Komentarze
Przejdź do komentarzy bezpośrednio na Githubie.