Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Component rendering since svelte 4 #9377

Closed
piolet opened this issue Nov 8, 2023 · 5 comments
Closed

Component rendering since svelte 4 #9377

piolet opened this issue Nov 8, 2023 · 5 comments

Comments

@piolet
Copy link

piolet commented Nov 8, 2023

Describe the problem

Since svelte 4 (if I'm not mistaken), the create_ssr_component method (in svelte/internal) is no longer available.
In my opinion, it was extremely useful. It allowed you to build components and obtain html rendering as well as css, which was ideal for dynamically building the html structure for an email, for example.

Is there a clean way of achieving the same result with the namespaces available?

Describe the proposed solution

Is it possible to make this method (create_ssr_component) available again, or an equivalent?

Alternatives considered

The only alternative I've found at the moment is to use svelte version 3 just for this email template project.

Importance

nice to have

@dummdidumm
Copy link
Member

create_ssr_component still exists, but we removed the types for it to further discourage using these internals.
I don't understand why you need to use this though - can't you just use the static render method that exists on components compiled with SSR? (it doesn't show up in the types aswell, but it's there).
In Svelte 5 this will definetely be removed in favor of a different approach. Could you lay out your use case in more details to see if there's anything we're missing?

@piolet
Copy link
Author

piolet commented Nov 8, 2023

@dummdidumm thanks for your reply.

My use case is :

import mjml2html from "mjml";
import type { SvelteComponentTyped } from "svelte";
import type { create_ssr_component } from "svelte/internal";

/**
 * Removes classes added to elements by the Svelte compiler because MJML does
 * not support them.
 */
const stripSvelteClasses = (html: string) =>
  html.replaceAll(/class="s-[\w-]+"/g, "");

/** Renders a Svelte component as email-ready HTML. */
export const render = <Props extends Record<string, any>>(
  component: new (...args: any[]) => SvelteComponentTyped<Props>,
  props: Props
) => {
  const ssrComponent = component as unknown as ReturnType<
    typeof create_ssr_component
  >;

  // Render the component to MJML
  const { html: body, css, head } = ssrComponent.render(props);

  const mjml = `<mjml>
        <mj-head>
          ${stripSvelteClasses(head)}
          <mj-style>${css.code}</mj-style>
        </mj-head>
        <mj-body>${stripSvelteClasses(body)}</mj-body>
      </mjml>`;

  // Render MJML to HTML
  const { html, errors } = mjml2html(mjml);
  if (errors.length > 0) console.warn(errors);

  return html;
};

I would like get the html content of my components to put it on an email body.
And build the email html content on demand, with each time a new data set.

But, you talk about render method, how call this method ? Do you have a link in documentation about that please ?

@dummdidumm
Copy link
Member

Judging from your code you're not actually calling create_ssr_component, you're just using it to get proper typings so that the component contains the correct render method. I'd just put a more lax type cast in place, this API will not change in Svelte 4:

const ssrComponent = component as any as { render: (props: Props) => { html: string, css: { code: string }, head: string } }

In Svelte 5 you will do something like this:

import { render } from 'svelte/server';

...
const { html: body, css, head } = render(component, { props });

Closing this because there's an actionable workaround present.

@dummdidumm dummdidumm closed this as not planned Won't fix, can't repro, duplicate, stale Nov 8, 2023
@wingrunr21
Copy link

wingrunr21 commented Mar 6, 2024

@dummdidumm out of curiosity, with the removal of returning css from render in Svelte 5, how would the mjml use case that was articulated above work? I have a similar use case where I need to take the resulting styles and process them (using satori and @vercel/og to generate dynamic pngs from Svelte components).

I can inline the styles into the component but I'd end up paying the performance cost of that if I also wanted to render the component client side as a "preview". I'm not sure this strategy would work for the MJML use case though.

@piolet
Copy link
Author

piolet commented Jan 18, 2025

@dummdidumm out of curiosity, with the removal of returning css from render in Svelte 5, how would the mjml use case that was articulated above work? I have a similar use case where I need to take the resulting styles and process them (using satori and @vercel/og to generate dynamic pngs from Svelte components).

I can inline the styles into the component but I'd end up paying the performance cost of that if I also wanted to render the component client side as a "preview". I'm not sure this strategy would work for the MJML use case though.

If i can help. Since svelte 5, this my code to manage mjml and css :

import type { Component, ComponentProps, SvelteComponent } from "svelte";
import { render as svelteRender } from 'svelte/server';

/**
 * Removes classes added to elements by the Svelte compiler because MJML does
 * not support them.
 */
const stripSvelteClasses = (html: string) => html.replaceAll(/class="s-[\w-]+"/g, '')

/** Renders a Svelte component as email-ready HTML. */
export const render = <
	Comp extends SvelteComponent<any> | Component<any>,
	Props extends ComponentProps<Comp> = ComponentProps<Comp>,
>(
	component: Component<SvelteComponent<any>>,
	props: Props
) => {
	const {body: html, head } = svelteRender(component, { props })

	// create a regex to extract text between <style> tags in the head
	const styleRegex = /<style(.*)>(.*?)<\/style>/s
	// extract the css from the head
	const resultRegex = head.match(styleRegex)
	const css = `<style${resultRegex?.[1] || ''}>${head.match(styleRegex)?.[2] || ''}</style>`
	// remove the css from the head
	const cleanHead = head.replace(css, '')

	// Render the component to MJML
	// const { html, css, head } = ssrComponent.render(props)

	return {
		html: stripSvelteClasses(html)
			.replace(/>\s+</g, '><')
			.replace(/(\r\n|\n|\r)/gm, ''),
		css: head.match(styleRegex)?.[2] || '',
		head: stripSvelteClasses(cleanHead).replace(/(\r\n|\n|\r)/gm, '')
	}

This return is to use with this code :

const mjml = `<mjml>
	<mj-head>
		${head}
		<mj-style>${css}</mj-style>
	</mj-head>
	<mj-body>${body}</mj-body>
</mjml>`
// Render MJML to HTML
const { html, errors } = mjml2html(mjml)
if (errors.length > 0) console.warn(errors)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants