如何在Mjml中创建自定义标签

我们使用MJML标记语言来创建响应式的邮件模板, 而MJML和HTML一样提供一些标签和熟悉来组织模板的样式和结构,如
<mj-body><mj-section><mj-column><mj-text> 等, 那么如何创建自定义的MJML标签呢?

打开mjml github 源码,打开所有标签实现的源码,我们发现:

所有的tag实现都继承了BodyComponent, 所以BodyComponent是基础类,我们自定义标签也只需在该类的基础上继承并扩展,
我们实现一个简单的 并解释下一些可自定义的属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import { BodyComponent } from 'mjml-core';
export default class MjExample extends BodyComponent {
static dependencies = {
'mj-example': [], // 表示允许什么标签出现在mj-example标签里面
'mj-wrapper': ['my-example'], // 表示该标签允许出现在哪个标签里面
'mj-body': ['my-example'],
'mj-section': ['my-example'],
};
static allowedAttributes = {
'background-image': 'string', // 允许传进来的参数属性
};
static defaultAttributes = {
'background-image': 'https://via.placeholder.com/150x150', // 如果参数没有传进来给的默认的属性值
};
componentHeadStyle = () => { // 自定义的样式
return `
.my-banner {
padding: 0;
background-color: #ffffff;
background-image: url(${this.getAttribute('background-image')});
background-size: cover;
background-repeat: no-repeat;
height: 150px;
}
`;
};
render() {
return this.renderMJML(`
<mj-section padding="0">
<mj-text font-size="24px" line-height="28px" font-weight="bold" color="black">
{{title}}
</mj-text>
<mj-image css-class="my-banner" padding="0">
</mj-section>
`);
}
}

当该标签定义好之后,之后我们就可以在模板里使用它,如:

1
<mj-example background-image="https://via.placeholder.com/250x250" /> // 如果不用自定义图片,则可以不传background-参数

自定义好之后,当我们在创建template的时候如何能做到实时更新呢?答案是我们可以使用gulp watch然后build mjml 模板,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
const gulp = require('gulp');
const babel = require('gulp-babel');
const watch = require('gulp-watch');
const fs = require('fs');
const path = require('path');

const mjml2html = require('mjml');
const { registerComponent } = require('mjml-core');
const glob = require('glob');
const fsPath = require('fs-path');
const componentPath = path.normalize('comp/**/*.ts'); // 自定义标签的组件路径
const templatePath = 'tmpl/**/*.mjml'; // 邮件模板路径

function handleError(err) {
console.log(err.toString());
process.exit(-1);
}

const walkSync = (dir, filelist = []) => {
fs.readdirSync(dir).forEach((file) => {
filelist = fs.statSync(path.join(dir, file)).isDirectory()
? walkSync(path.join(dir, file), filelist)
: filelist.concat(path.join(dir, file));
});
return filelist;
};

const watchedComponents = walkSync('./comp'); // 自定义标签的组件路径

const compile = () =>
gulp
.src(componentPath)
.pipe(
babel({
presets: ['@babel/preset-env'], //这边添加babel parser
})
)
.on('error', handleError)
.pipe(gulp.dest('lib'))
.on('end', () => {
watchedComponents.forEach((compPath) => {
// 找到自定义tag的component并在全局注册
const distPath = compPath.substr(0, compPath.lastIndexOf('.')) + '.js';
const fullPath = path.join(process.cwd(), distPath.replace(/^comp/, 'lib'));
delete require.cache[fullPath];
registerComponent(require(fullPath).default);
});
glob(templatePath, (err, files) => {
if (err) throw err;
files.forEach((file) => {
const templateName = file.split('/')[2];
fs.readFile(file, 'utf8', (error, data) => {
if (error) throw error;
const result = mjml2html(data, { beautify: true }); // 生成html
fsPath.writeFileSync(path.normalize(`dist/${templateName}.html`), result.html); // 写入HTML文件
});
});
});
});
gulp.task('build', compile);
gulp.task('watch', () => {
compile();
return watch([componentPath, path.normalize(templatePath)], compile);
});