sveltejs / svelte

Cybernetically enhanced web apps
https://svelte.dev
MIT License
78.66k stars 4.13k forks source link

Extended HTML rendering #6346

Closed madeleineostoja closed 3 years ago

madeleineostoja commented 3 years ago

Is your feature request related to a problem? Please describe. There's no clear way to parse HTML strings and enrich it/substitute nodes for svelte components before its rendered. For example consuming CMS content (enriching links with sveltekit:prefetch, using svelte components for images/headings/etc, path resolving, etc).

Describe the solution you'd like Given that Svelte is so perfectly suited to lightweight websites, this might be something that would be worth supporting out of the box, maybe by extending/supplementing the @html render method, with one that gives the user access to the AST/whatever to modify before the compiler turns it into HTML.

Describe alternatives you've considered Sidestepping Svelte's inbuilt HTML rendering, and parsing a HTML string into an AST yourself, modifying it somehow, then using something like the tools in MDSveX to turn it into a Svelte component to render.

How important is this feature to you? Reasonably important. Currently Svelte is very difficult to fully integrate with a CMS backend, which for us is by far the biggest use case vs React et al.

dummdidumm commented 3 years ago

This sounds like a use case for preprocessors. Basically, before you hand off the file to the compiler, you can perform any kind of logic on the given input. If you are talking about doing this at runtime, then I'd say this is not possible, because since Svelte is a compiler, the Svelte code has to be compiled to JS code before arriving in the browser.

madeleineostoja commented 3 years ago

Ah okay, there isn't a huge amount of documentation for preprocessors, but am I understanding it right if I just changed an incoming markup block from something like

<h1>Title</h1>
<p>Some text <a href="...">link</a></p>

To (assuming Title is imported in the right places)

<Title>Title</Title>
<p>Some text <a sveltekit:prefetch href="...">link</a></p>

It would be compiled correctly by Svelte? How about HTML strings rendered by {@html content}? I assume they're indistinguishable from component markup? In which case a preprocessor would need fragile markers of some sort added by the user to only apply it to HTML strings rather than component markup.

In any case maybe we could use some more documentation around preprocessors? This is a use-case I hadn't even considered using them for, and I'm familiar with them (at least as a user of them, if not an author of any)

dummdidumm commented 3 years ago

Yes, this is what you could do - assuming that you do these transformations at build time before handing the code off to the svelte compiler. So you could not do these transformations in the browser, because at that time it's already "too late". The Svelte Society site has a recipe on writing preprocessors.

HTML strings rendered by @html need to be simple HTML, they can't contain Svelte-specific things. This is the runtime-constraint I was talking about earlier.

Does this answer your questions or is there something left as an actual task? We are in general hesitant to add documentation like this to the official site because where do we draw the line on such recipe-like documentation if we start doing it?

madeleineostoja commented 3 years ago

HTML strings rendered by @html need to be simple HTML, they can't contain Svelte-specific things. This is the runtime-constraint I was talking about earlier.

Okay so is @html rendered at runtime? Are the HTML strings it (pre)renders not accessible in a preprocessor? In that case a preprocessor isn't the answer to this use-case. And if those strings are available in a preprocessor then is there a way to target them specifically? If not then preprocessors also aren't an answer to this use-case, because we need a way to enrich 3rd party HTML / HTML strings, not component template markup as a blanket thing.

Example

<h1>Some component</>
<article>
  <!-- HTML content to enrich with Svelte actions/components/etc -->
  {@html cmsContent}
</article>

As for documentation, totally understand not wanting to add recipes to the official docs (though maybe links out to those recipes/community resources would be good? Unless I missed that in the docs). Still though I'd argue the current svelte.preprocess docs are lacking. For example this very issue about how @html works in that context, and maybe I'm just dense but I didn't realise you could just pass back Svelte components/templates as strings and have them be rendered properly.

dummdidumm commented 3 years ago

It feels to me we are talking past each other a little. Preprocessing and compiling Svelte components needs to happen at build time, not at runtime. @html can be handed any string of html at runtime and it will render it accordingly, but Svelte specific things like components or actions or if statements cannot appear in it, or more precisely they are just treated as html, not as Svelte code. So the snippet you posted won't work.

That means you can't load your CMS code into the browser, preprocess and transform it to Svelte code, then compile it to JS and then run it ( well technically you might be able to, the REPL does this, but you surely don't want to load 5 MB of compiler code). These things need to happen at build time.

This works: At build time: Fetch CMS code -> preprocess to Svelte code -> compile with the Svelte compiler to either prerendered pages (html) or dynamic code (js) --> user goes to your site and gets the result

This doesn't work: User goes to your site -> CMS code is fetched from browser -> preprocess to Svelte code -> compile with the Svelte compiler to JS -> render result

This would work, if you need dynamic behavior: Write a Svelte library, which gets cms code in a more defined way, like JSON, and transforms this into a visual representation. Example:

<script>
  export let cmsCode; // = { heading: 'Foo', content: [{text: 'bar'}, {text: 'link', href: '..']}
</script>

<Heading>{cmsCode.heading}</Heading>
{#each content as part}
  {#if part.href}
    <a href={part.href}>{part.text}</a>
  {:else}
    <span>{part.text}</span>
  {/if}
{/each}

An alternative is to write the HTML code dynamically yourself an then pass it to @html like the Svelte site does it (example how a blogpost.md file is transformed)

I'm going to close this since there isn't something actionable for the Svelte core repo, but feel free to continue to answer.