Closed ChronicStone closed 2 years ago
Thanks for the detailed description and for attaching the files.
I'm not seeing any <style>
tag in the Node build, so I'm thinking all CSS gets purged right in Tailwind.
Can you share your Tailwind and Maizzle configs?
It should not purge classes that you have in the template
string, because it's automatically handled:
Also, you do have {{ page.css }}
in your layout, right?
Hi @cossssmin
I do have the {{{ page.css }}} in my layout. I also do not have any purging config defined on my tailwind config. Here's all you requested :
My maizzle config on node :
{
env: 'node',
// inlineCSS: true,
build: {
components: {
root: EMAILS_ROOT_PATH,
},
layouts: {
root: EMAILS_ROOT_PATH,
}
}
}
My tailwindcss config :
module.exports = {
mode: 'jit',
theme: {
screens: {
sm: {max: '600px'},
md: {max: '800px'},
lg: {max: '1000px'},
xl: {max: '1200px'},
},
fontFamily: {
body: ['bilo', 'sans-serif']
},
extend: {
colors: {
primary: '#fff',
secondary: '#168a99',
secondaryDark: '#116b77'
},
width: {
'9/10': '90%'
},
spacing: {
screen: '100vw',
full: '100%',
px: '1px',
0: '0',
2: '2px',
3: '3px',
4: '4px',
5: '5px',
6: '6px',
7: '7px',
8: '8px',
9: '9px',
10: '10px',
11: '11px',
12: '12px',
14: '14px',
16: '16px',
20: '20px',
24: '24px',
28: '28px',
32: '32px',
36: '36px',
40: '40px',
44: '44px',
48: '48px',
52: '52px',
56: '56px',
60: '60px',
64: '64px',
72: '72px',
80: '80px',
96: '96px',
600: '600px',
'1/2': '50%',
'1/3': '33.333333%',
'2/3': '66.666667%',
'1/4': '25%',
'2/4': '50%',
'3/4': '75%',
'1/5': '20%',
'2/5': '40%',
'3/5': '60%',
'4/5': '80%',
'1/6': '16.666667%',
'2/6': '33.333333%',
'3/6': '50%',
'4/6': '66.666667%',
'5/6': '83.333333%',
'1/12': '8.333333%',
'2/12': '16.666667%',
'3/12': '25%',
'4/12': '33.333333%',
'5/12': '41.666667%',
'6/12': '50%',
'7/12': '58.333333%',
'8/12': '66.666667%',
'9/12': '75%',
'10/12': '83.333333%',
'11/12': '91.666667%',
},
borderRadius: {
none: '0px',
sm: '2px',
DEFAULT: '4px',
md: '6px',
lg: '8px',
xl: '12px',
'2xl': '16px',
'3xl': '24px',
full: '9999px',
},
fontFamily: {
sans: ['ui-sans-serif', 'system-ui', '-apple-system', '"Segoe UI"', 'sans-serif'],
serif: ['ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 'serif'],
mono: ['ui-monospace', 'Menlo', 'Consolas', 'monospace'],
},
fontSize: {
0: '0',
xs: '12px',
sm: '14px',
base: '16px',
lg: '18px',
xl: '20px',
'2xl': '24px',
'3xl': '30px',
'4xl': '36px',
'5xl': '48px',
'6xl': '60px',
'7xl': '72px',
'8xl': '96px',
'9xl': '128px',
},
inset: theme => ({
...theme('spacing'),
}),
letterSpacing: theme => ({
...theme('spacing'),
}),
lineHeight: theme => ({
...theme('spacing'),
}),
maxHeight: theme => ({
...theme('spacing'),
}),
maxWidth: theme => ({
...theme('spacing'),
xs: '160px',
sm: '192px',
md: '224px',
lg: '256px',
xl: '288px',
'2xl': '336px',
'3xl': '384px',
'4xl': '448px',
'5xl': '512px',
'6xl': '576px',
'7xl': '640px',
}),
minHeight: theme => ({
...theme('spacing'),
}),
minWidth: theme => ({
...theme('spacing'),
}),
},
},
corePlugins: {
animation: false,
backgroundOpacity: false,
borderOpacity: true,
divideOpacity: true,
placeholderOpacity: false,
textOpacity: false,
},
}
My layout file :
<!DOCTYPE {{{ page.doctype || 'html' }}}>
<html lang="{{ page.language || 'en' }}" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
<meta charset="{{ page.charset || 'utf-8' }}">
<meta name="x-apple-disable-message-reformatting">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
<link rel="stylesheet" href="https://use.typekit.net/dil2ent.css" />
<!--[if mso]>
<noscript>
<xml>
<o:OfficeDocumentSettings xmlns:o="urn:schemas-microsoft-com:office:office">
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
</noscript>
<style>
td,th,div,p,a,h1,h2,h3,h4,h5,h6 {font-family: "Segoe UI", sans-serif; mso-line-height-rule: exactly;}
</style>
<![endif]-->
<if condition="page.title">
<title>{{{ page.title }}}</title>
</if>
<if condition="page.css">
<style>{{ page.css }}</style>
</if>
<block name="head"></block>
</head>
<body class="{{ page.bodyClass }} font-body">
<if condition="page.preheader">
<div class="hidden">{{{ page.preheader }}}͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ‌
 ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ‌
 ͏ ͏ ͏ ͏ ͏ </div>
</if>
<div role="article" class="w-full h-full justify-center text-black" aria-roledescription="email" aria-label="{{{ page.title || '' }}}" lang="{{ page.language || 'en' }}">
<table class="w-full">
<tr>
<td align="center">
<div class="w-2/4 md:w-9/10">
<table class="text-black pt-32 ml-20 text-left">
<tr>
<td align="center">
<!-- HEADER -->
<tr class=" justify-between w-full items-center">
<img src="images/logo_vtest.png" class="h-48"/>
</tr>
<if condition="page.title" class="text-center">
<table class="text-3xl w-full font-bold mt-16">
<tr>
<td align="center">
<div class="max-w-4xl text-center uppercase">
{{{ page.title }}}
</div>
<if condition="page.icon">
<img src="images/{{page.icon}}.png" class="w-40 my-20">
</if>
</td>
</tr>
</table>
</if>
<!-- CONTENT -->
<table>
<tr>
<td>
<block name="template"></block>
</td>
</tr>
</table>
<hr class="border-0 bg-gray-200 text-gray-200 h-px w-full my-28"/>
<!-- FOOTER -->
<div class="text-center -col gap-16 text-sm">
<!-- 'RECEIVED AS CANDIDATE' MENTION -->
<if condition="page.target === 'candidate'">
<span class="text-secondary font-bold text-lg">$t('LAYOUT.EMAIL_TARGET')</span>
</condition>
<!-- TEST CENTER INFORMATION -->
<div class="mt-16 text-black">
<div class="font-bold mb-2">$t('LAYOUT.TEST_CENTER_CONTACT_SENTENCE')</div>
<div><%test_center.name%></span>
<div><a href="" class="text-black no-underline hover:text-secondary"><%test_center.email%></a> - <%test_center.phone_number%></span>
<div><%test_center.address%></span>
</div>
<hr class="border-0 bg-gray-200 text-gray-200 h-px w-3/4 self-center my-12"/>
<div class=" -col mb-16">
<div>$t('LAYOUT.KNOW_MORE.PART_1') <a href="https://www.vtest.com/privacy-policy/" class="text-black no-underline hover:text-secondary">VTEST privacy policy</a> - Copyright 2021 - VTEST</div>
<div>
<a href="https://vtest.com" class="text-black no-underline hover:text-secondary">www.vtest.com</a>
</div>
</div>
</div>
</td>
</tr>
</table>
</div>
</td>
</tr>
</table>
</div>
</body>
</html>
Hmm, I wonder if it's because of the environment variables for layouts/components.
I've just tested this simple scenario and it works fine:
// node-test.js
const Maizzle = require('@maizzle/framework')
const template = `
<!DOCTYPE html>
<html>
<head>
<if condition="page.css">
<style>{{{ page.css }}}</style>
</if>
</head>
<body>
<div class="text-base sm:text-sm">test</div>
</body>
</html>
`
Maizzle.render(
template,
{
maizzle: {
env: 'node',
inlineCSS: true,
removeUnusedCSS: true,
},
tailwind: {
css: '@tailwind utilities;',
config: import('./tailwind.config.js'), // default Tailwind config from https://github.com/maizzle/maizzle
}
}
)
.then(({html}) => {
console.log(html)
})
node node-test.js
Result:
<!DOCTYPE html>
<html>
<head>
<style>
@media (max-width: 600px) {
.sm-text-sm {
font-size: 14px !important;
}
}
</style>
</head>
<body>
<div class="sm-text-sm" style="font-size: 16px;">test</div>
</body>
</html>
I'll test some more with layouts and components, though that should work fine too.
Btw, what are you using for translations?
Added a Node.js example repo:
Thanks for the example. Unfortunately I used the exact same Maizzle config as you did in the example, and I still get the same result. I also tried switching to different versions of Maizzle, but it did not help either.
Here's a template example of one of my templates, with its variables :
---
template_name: exam_done
title: $t('TEMPLATE.EMAIL_TITLE')
preheader:
bodyClass: bg-gray-100
icon: done
target: candidate
layout: exam
---
<extends src="src/layouts/exam.html">
<block name="template">
<div class="w-full">
<!-- INTRO SECTION -->
<div>
$t('TEMPLATE.EXAM_COMPLETION_SENTENCE.PART_1')
$t('TEMPLATE.EXAM_COMPLETION_SENTENCE.PART_2')
</div>
<!-- EXAM INFORMATION SECTION -->
<div class="mt-16">
$t('TEMPLATE.RESULTS_ACCESS_INSTRUCTIONS')
</div>
<table class="w-full my-16">
<tr>
<td align="center">
<!-- CTA SECTION -->
<div>
<span class="font-bold uppercase">$t('TEMPLATE.SECURE_CODE') :</span> @{{assessment.secure_code}}
</div>
<div class="mt-12">
<component locals='{"link": "@{{resources.result_app_url}}"}' src="src/components/button.html">
$t('TEMPLATE.ACCESS_RESULTS_BUTTON')
</component>
</div>
</td>
</tr>
</table>
<!-- SECTION -->
<div>
$t('TEMPLATE.RESULT_PAGE_CONTENT')
</div>
<!-- END SECTION -->
<div class="mt-16">
$t('TEMPLATE.THANKS_MESSAGE')
</div>
</div>
</block>
</extends>
About translations, what I needed for this project was really simple so I custom-made my locales injection system : (Small explanation, the template & layout variables of the destructured object in parameters contains the translations for the targetted locale, and the default-prefixed variables contains the default ones. Pretty simple implementation that makes sure if a translation / translation key is not available in the selected language, we display the default one)
Does the example work for you as-is? Wondering if it has to do with something else in your code...
I think this might be related to the issue i'm having as well which is that styles don't get inlined when using templates. Could you create a node example that uses templating?
Was definitely a struggle to drill down to this being the issue (validated that it works so long as you don't try to extend a template). I have a feeling it's related to this line in the docs:
@cossssmin looks like @digitalmaster is right about the templates not rendering the extended blocks.
I feel like this is something to do with the render of the layout which doesn't get any styles applied to it and it removes all unused CSS after the render.
Honesty I have the same issue right, I love the render but looks like I will need to build production emails and then serve them via render
Could you provide a repo that reproduces the issue? Can’t help much without one, sorry.
For me this issue was related to the fact that Maizzle is nested in a subdirectory ~/emails/
.
This is fine for maizzle serve
because we run it from this emails directory (meaning working directory is correctly set to ~/emails
).
However, when we trigger Maizzle.render()
, the working directory is that of the Node process that triggered it (which is the root of my NextJs project ~/
). This bad root causes all CSS to be purged since it can't find any HTML to tell it what to keep.
The fix was pretty straight forward: Just configure root
values:
commit 450201badf7fe4e2be35e368bab30fbd83308901
Author: Jose Browne <josebrowne@gmail.com>
Date: Mon Feb 7 11:15:04 2022 -0400
Set Template Root Directory Based on Environment
diff --git a/curriculum/curriculum/emails/config.js b/curriculum/curriculum/emails/config.js
index c59407f..442859e 100644
--- a/curriculum/curriculum/emails/config.js
+++ b/curriculum/curriculum/emails/config.js
@@ -10,6 +10,8 @@
*/
const fs = require('fs/promises');
+const isLocalEmailDevEnv = process.env.NODE_ENV === 'local';
+
module.exports = {
build: {
templates: {
@@ -22,6 +24,14 @@ module.exports = {
destination: 'images',
},
},
+ ...(!isLocalEmailDevEnv && {
+ layouts: {
+ root: 'emails/',
+ },
+ components: {
+ root: 'emails/',
+ },
+ }),
browsersync: {
port: 3001,
ui: { port: 3002 },
hope this helps 🙏
I am having a similar problem.
Like @digitalmaster all my ressources (layouts, css, components, tailwind.config.js) are in a subdirectory named emails
.
My render function looks like this :
const { html } = await Maizzle.render(template, {
maizzle: {
env: 'node',
inlineCSS: {
mergeLonghand: true
},
prettify: {
ocd: true
},
build: {
components: {
root: './emails'
},
layouts: {
root: './emails'
}
}
},
tailwind: {
css: `@tailwind utilities;`,
config: import('../emails/tailwind.config.js')
}
})
The problem is that my layout and components are not handled by maizzle/tailwind. Every class stays in its place, neither purged nor inlined, which in the end produces a template with no styling for those layout and components.
The funny thing is that everything works fine when this subdirectory is named src
(and that the corresponding paths are updated). The render function then looks like this :
const { html } = await Maizzle.render(template, {
maizzle: {
env: 'node',
inlineCSS: {
mergeLonghand: true
},
prettify: {
ocd: true
},
build: {
components: {
root: './'
},
layouts: {
root: './'
}
}
},
tailwind: {
css: `@tailwind utilities;`,
config: import('../src/tailwind.config.js')
}
})
I fail to see where the problem comes from, I lost hope and currently have my directory called src
but would really like to find a solution !
When using the NodeJS version, a big part of the CSS is not rendered. When using inline mode, more than half classes are simply ignored, and when not inlined, the CSS associated to most classes is not generated as well.
NODEJS CONFIG & SETUP :
And using the exact same config on Node and with Maizzle CLI, but the output of the builds is totally different :
MAIZZLE CLI BUILD:
NODEJS BUILD:
Here's an archive with the HTML outputs of the email template generated through Node and Maizzle CLI HTML BUILDS.zip