11ty / eleventy

A simpler site generator. Transforms a directory of templates (of varying types) into HTML.
https://www.11ty.dev/
MIT License
17.18k stars 494 forks source link

Certain code blocks in markdown get parsed as liquid template after upgrading to 1.0 #2273

Closed carbontwelve closed 3 months ago

carbontwelve commented 2 years ago

Describe the bug I have a Vue.js template example within a markdown code block, as seen here in my blogs source and in the below screenshot:

image

In upgrading to eleventy 1.0 I now get a parse error which didn't occur in previous versions:

[11ty] > Having trouble rendering liquid template ./posts/2018-05-03-build-an-incremental-web-game-with-vue-js.md

`TemplateContentRenderError` was thrown
[11ty] > unexpected token at "? '/' + units...", file:./posts/2018-05-03-build-an-incremental-web-game-with-vue-js.md, line:285, col:96

`ParseError` was thrown
[11ty] > unexpected token at "? '/' + units..."

Having thrown a debugger on the process it looks as though the markdown isn't being parsed and therefore the code examples aren't going via @11ty/eleventy-plugin-syntaxhighlight before the file's content is parsed by the liquid template parser.

image

As you can see here, its attempting to tokenise the unparsed markdown; the vue template syntax is similar to liquid and so it's attempting to tokenise it when it shouldn't

image

To Reproduce Steps to reproduce the behavior:

  1. Clone this branch of my blog https://github.com/photogabble/website/tree/feature/67-upgrade-eleventy
  2. Run npm run build
  3. See error

Is there something I am missing in my configuration that needs to tell 11ty that md files need to be parsed by the markdown lib and prism before being passed to the liquid template parser?

This wasn't an issue in 0.12.1, deleting the problematic markdown file results in the website building correctly therefore I assume the error is limited to the scope highlighted in my screenshots above.

carbontwelve commented 2 years ago

Just checked and this still happens in 2.0.0-canary.3 although with a nicer error output (nice work!)

[11ty] Problem writing Eleventy templates: (more in DEBUG output)
[11ty] 1. Having trouble rendering liquid template ./posts/2018-05-03-build-an-incremental-web-game-with-vue-js.md (via TemplateContentRenderError)
[11ty] 2. unexpected token at "? '/' + units...", file:./posts/2018-05-03-build-an-incremental-web-game-with-vue-js.md, line:285, col:96 (via ParseError)
[11ty] 3. unexpected token at "? '/' + units..." (via AssertionError)
[11ty] 
[11ty] Original error stack trace: AssertionError: unexpected token at "? '/' + units..."
[11ty]     at new AssertionError (/Users/simon/Code/photogabble/website/node_modules/liquidjs/dist/liquid.node.cjs.js:679:28)
[11ty]     at assert (/Users/simon/Code/photogabble/website/node_modules/liquidjs/dist/liquid.node.cjs.js:836:15)
[11ty]     at Tokenizer.readFilter (/Users/simon/Code/photogabble/website/node_modules/liquidjs/dist/liquid.node.cjs.js:1958:9)
[11ty]     at Tokenizer.readFilters (/Users/simon/Code/photogabble/website/node_modules/liquidjs/dist/liquid.node.cjs.js:1947:31)
[11ty]     at new Value (/Users/simon/Code/photogabble/website/node_modules/liquidjs/dist/liquid.node.cjs.js:2432:34)
[11ty]     at new Output (/Users/simon/Code/photogabble/website/node_modules/liquidjs/dist/liquid.node.cjs.js:4164:23)
[11ty]     at Parser.parseToken (/Users/simon/Code/photogabble/website/node_modules/liquidjs/dist/liquid.node.cjs.js:4225:24)
[11ty]     at Parser.parseTokens (/Users/simon/Code/photogabble/website/node_modules/liquidjs/dist/liquid.node.cjs.js:4215:33)
[11ty]     at Parser.parse (/Users/simon/Code/photogabble/website/node_modules/liquidjs/dist/liquid.node.cjs.js:4209:21)
[11ty]     at Liquid.parse (/Users/simon/Code/photogabble/website/node_modules/liquidjs/dist/liquid.node.cjs.js:4339:28)
[11ty] Copied 6 files / Wrote 0 files in 0.61 seconds (v2.0.0-canary.3)
pdehaan commented 2 years ago

Interesting. I think .md files are processed in liquidjs by default, per https://www.11ty.dev/docs/config/#default-template-engine-for-markdown-files. You could try setting markdownTemplateEngine: false in your config file; although I'm uncertain why that would have changed between 0.12.x and 1.0. I didn't see anything obvious in the 1.0 release notes.

I'll try fetching your repo and poking at it later today and see if I can give you a better response than "wrap the Vue code in a {% raw %}..{% endraw %} block".

pdehaan commented 2 years ago

"Good" news: I can reproduce the above errors in v1, but no errors if I downgrade to 0.12. "Bad" news: I don't think it worked in 0.12 either, but the older (v6?) version of liquidjs failed a bit more silently.

https://github.com/photogabble/website/blob/07465c69461f16a8be0a7ac4062cd2d7f110d322/posts/2018-05-03-build-an-incremental-web-game-with-vue-js.md?plain=1#L62-L70

<template>
    <div id="app">
        <h1>Ore Reserves: {{ ore }}</h1>
        <br>
        <button>Mine Ore</button>
    </div>
</template>

But I build locally in v0.12 and view the http://localhost:8080/blog/tutorials/build-an-incremental-clicker-web-game-with-vuejs-part-one/#implementing-a-basic-game-mechanic page, I see the following:

<template>
    <div id="app">
        <h1>Ore Reserves: </h1>
        <br>
        <button>Mine Ore</button>
    </div>
</template>

Note how liquid is trying to process the {{ ore }} VueJS code, but then silently failing.

I think I managed to potentially fix 0.12 by setting templateEngineOverride: md in the front matter for posts/2018-05-03-build-an-incremental-web-game-with-vue-js.md. I tried my suggestion of setting markdownTemplateEngine: false globally in the .eleventy.js config file, but that seemed to break the permalinks because now liquidjs wasn't processing the posts/posts.json file's permalink key which included some LiquidJS tags of "permalink": "blog/{{ categories | first }}/{{ title | slugify }}/index.html",, which threw some confusing results like:

> Output conflict: multiple input files are writing to `_site/blog/{{ categories | first }}/{{ title | slugify }}/index.html`. Use distinct `permalink` values to resolve this conflict.
  1. ./posts/2021-01-03-a-focus-on-birdsite-drama.md
  2. ./posts/2009-06-10-mission-mainframe.md
  ...
  49. ./posts/2021-01-01-if-dos-game-dev-is-a-rabbit-hole-then-call-me-alice.md

Unfortunately, it doesn't look like you can globally set the "templateEngineOverride": "md" in your posts/posts.json file, and it might need to be added to each template individually. Of course, take it all w/ a grain of salt. I just spot checked that one file, I didn't check each blog post and see if you were using liquidjs tags/filters elsewhere to see if I broke something unexpected.

But after seeing it [theoretically] fixed in 0.12, I upgraded to 1.0.0 and I didn't see build errors anymore, so that might have been the only template using liquidjs-looking-but-actually-vuejs variables. So you probably don't need to add it in all your posts/*.md files.

Finally, if you want my promised disappointing response of "Just use {% raw %}...{% endraw %}, that seemed to work for me without needing to disable liquidjs processing of the markdown file in question. I had to change 3 code blocks, but it wasn't a huge deal, and looked something like this:

```html
{%- raw -%}
<template>
    <div id="app">
        <h1>Ore Reserves: {{ ore }}</h1>
        <h1>Mines: {{ mines }}</h1>
        <h1>Colonists: {{ colonists }}</h1>
        <h1>Food: {{ food }}</h1>
        <br>
        <button v-on:click="mineOre">Mine Ore</button>
    </div>
</template>
{% endraw -%}

And still rendered out some HTML like this:

```html
<template>
    <div id="app">
        <h1>Ore Reserves: {{ ore }}</h1>
        <h1>Mines: {{ mines }}</h1>
        <h1>Colonists: {{ colonists }}</h1>
        <h1>Food: {{ food }}</h1>
        <br>
        <button v-on:click="mineOre">Mine Ore</button>
    </div>
</template>

Versus the current, live version at https://photogabble.co.uk/blog/tutorials/build-an-incremental-clicker-web-game-with-vuejs-part-one/#adding-the-colonists

<template>
    <div id="app">
        <h1>Ore Reserves: </h1>
        <h1>Mines: </h1>
        <h1>Colonists: </h1>
        <h1>Food: </h1>
        <br>
        <button v-on:click="mineOre">Mine Ore</button>
    </div>
</template>
pdehaan commented 2 years ago

If you want to disable liquidjs processing of the markdown files, it looks like you CAN set markdownTemplateEngine: false in your .eleventy.js config file, but then you'll need to rename your posts/posts.json file to posts/posts.11tydata.js and set the permalink using JavaScript instead of liquidjs templating:

// posts/posts.11tydata.js
module.exports = {
  // "permalink": "blog/{{ categories | first }}/{{ title | slugify }}/index.html",
  featured: false,
  layout: "layouts/post.njk",
  eleventyComputed: {
    permalink(data) {
      return `blog/${ data.categories[0] }/${ this.slugify(data.title) }/`
    }
  }
};
carbontwelve commented 2 years ago

@pdehaan Thank you for looking into this in so much detail, I hadn't noticed that the code examples weren't displaying correctly - this was likely broken in the previous static site generators I used.

I have a figure shortcode that I will eventually get round to refactoring posts to use in place of images therefore it seems your {%- raw -%} solution is the way to go until this is possibly fixed in Eleventy.

pdehaan commented 2 years ago

until this is possibly fixed in Eleventy.

Honestly, I'm not sure this is a bug. Markdown files are preprocessed by LiquidJS (or Nunjucks or your engine of choice). But the fact that JSX or Vue or whatever uses a familiar and overlapping {{ ... }} syntax is an unfortunate inconvenience which can lead to circumstances like this. Eleventy would have no idea if your template meant to use a LiquidJS variable or a Vue code snippet or something else. I'd argue that the more recent strict behavior of LiquidJS throwing errors instead of silently failing is a very good step in the right direction since it can highlight these issues.

I'd argue that the solution is to use {% raw %} (why the filter exists), or else disable Markdown preprocessing if it isn't a feature you want/need.

carbontwelve commented 2 years ago

Honestly, I'm not sure this is a bug.

I heartily agree. I think you're right that this is an unfortunate edge case due to the overlapping syntax.

While I can't say this is a "common pitfall" I certainly consider it warrants a small section in https://www.11ty.dev/docs/pitfalls/

pdehaan commented 2 years ago

While I can't say this is a "common pitfall" I certainly consider it warrants a small section in 11ty.dev/docs/pitfalls

OOooohhh, excellent suggestion! PRs welcome over in the 11ty/11ty-website repo.

regisphilibert commented 2 years ago

As mentioned in https://github.com/11ty/eleventy/issues/2273#issuecomment-1065837671 I confirm templateEngineOverride settings is only working if added from the content file directly... not as a directory data file...

csi-lk commented 1 year ago

This is a really big gotcha that prevented me from updating for a while, agree with adding it to the common pitfalls section

As these are contained in markdown code blocks is there a way to get the LiquidJS templating engine to ignore {{}} when within a "code block" (maybe even as a setting)?

(I wouldn't know where to start with this but am willing to put some time into it if someone can point me in the right direction)

YUN-RU-TSENG commented 7 months ago

have same issue

zachleat commented 3 months ago

Please use {% raw %} if you want Liquid or Nunjucks to ignore a block of code! Otherwise, you do have full control over the preprocessing language here: https://www.11ty.dev/docs/languages/#overriding-the-template-language

https://liquidjs.com/tags/raw.html

jeremenichelli commented 1 month ago

Just in case somebody comes back to this, my solution was to pass markdown-it as the md engine in the config.

I did it originally because I need some custom stuff around link parsing, but it ended up preventing this issue. Not sure what's going on behind the scenes, I am guessing liquid.js let's its markdown engine to parse thing for it and delegate to liquid rendering when encounter things like that, but overriding the markdown engine undoes this 🤷

carbontwelve commented 1 month ago

@jeremenichelli I think that is because the default parser config for md files is markdown|liquid. This means 11ty will parse the file first as a markdown document and then as a liquid template nessesitating the use of {% raw %}.