最终效果
背景
缘起
据知情人介绍,后端的发送邮件的服务是后端发送一个.html文件(后端写的)来实现,由于不同的邮件平台,对这个html的解析是不尽相同的,样式频繁出现不适配的情况。
好了! 后端接锅就不住了,改样式兼容的东西自然落到了前端头子身上,前端头子哪有这个空?这么大个锅又自然地落在了“前端切图仔”身上。
心死
适配html邮件第一件事就是:把css都写成内联样式!!
与普通 HTML 页面开发一样,HTML 邮件依旧离不开 CSS,HTML 邮件并不支持外部的 style 文件,head 标签极有可能被删除,所以不要试图在 head 标签内写 style 标签。 那么在 body 内写 style 标签是不是就保险了呢?并不是!典型的就是 Gmail 邮箱,会把 HTML 邮件内所有 style 标签删除,这就意味着只有內联 style 属性内的 CSS 是唯一可靠的样式信息。
好家伙,100多个这样的html文件,改完我直接羽化登仙~,按一个文件8min算吧(还不包括跑脚本测试的时间),这波操作下去,真体力活呀。
不干体力活
不干体力活,“切图仔”也是有尊严的。于是转念一想,既然100多个文件要做的事情一样,那先一个CLI命令行批量去处理不就完了? 目标:执行html2email gen,自动把html转为邮件平台适配的样式
场景
基于html邮件,我们看一下css内联样式的一般场景
- 发送邮件
- 在第三方网站中嵌入HTML
- 从其他编辑器拷贝编辑好的文章发布到微信、今日头条等自媒体
在以上场景使用行内样式的兼容性要高很多,也可以保持原样式不变,加载速度也更快。目前的解决方案有 juice 和 inline-css(感谢开源!)
CLI项目搭建
step1:初始化项目
创建一个空项目(eos-cli),使用yarn init 进行初始化
yarn init
// 一直回车就完事,其他东西后面再改
支持TypeScript&Rollup打包
强烈建议用TypeScript做强类型检查以及非常友好的提示。
比较详细的配置上篇文章有写一些。
// yarn安装
yarn add tslib typescript -D
// 生成ts配置文件,tsconfig.json可用来配置编译成的js版本、tslint检查等
npx tsc --init
// 安装rollup,并新建rollup.config.js配置文件
yarn add rollup -D
bin执行脚本
node.js 内置了对命令行操作的支持,它是一个命令名和本地文件名的映射。在安装时,如果是全局安装,npm将会使用符号链接把这些文件链接到{{prefix}}/bin,如果是本地安装,会链接到./node_modules/.bin/。
通俗点理解就是我们全局安装, 我们就可以在命令行中执行这个文件, 本地安装我们可以在当前工程目录的命令行中执行该文件。
"bin": {
"html2email": "bin/index.js"
},
要注意: 这个index.js文件的头部必须有这个#!/usr/bin/env node指定当前脚本由node.js进行解析
#!/usr/bin/env node
//../lib/bundle.cjs.js 是打包后的文件
require('../lib/bundle.cjs.js')
目录结构
├── bin
│ └── index.js // bin可执行文件
├── lib
├── ... // 打包后的文件
└── src
├── genHtmlEmail.ts // 处理,转换html的方法
├── index.ts // 主流程入口文件
├── .babelrc // babel配置文件
├── package.json
├── rollup.config.js // rollup打包配置文件
├── tsconfig.json // ts配置文件
├── yarn.lock
├── README.md
step2:html2email命令行使用
启动项目
// 执行yarn build
yarn build
// package.json scripts(-w 监听文件变更,有变更自动打包)
"scripts": {
"build": "rollup -c -w"
},
全局使用
在当前 目录下执行 npm link/ yarn link,将 html2email 命令链接到全局环境。
完成以上俩步之后,html2email就可以正常使用了
实现过程
新建了命令行项目之后,接下来是往项目里堆代码了,分为两个步骤
html2email命令行处理
使用cac来处理命令行,功能丰富且强大,cac npm
Command And Conquer is a JavaScript library for building CLI apps
import cac from 'cac'
const cli = cac()
// 当用户只输入html2email时,显示其他command的友好提示
cli.command('').action(() => {
cli.outputHelp()
})
// html2email gen时执行,支持传入option参数,来控制流程
cli
.command('gen', 'Generate target resources')
.option('-k, --keepFolderStructure', 'keep original folder structure')
.allowUnknownOptions()
.action(async (flags: Partial<CliOptions>) => {
const config = await getConfig(flags)
genHtmlEmail(config as CliOptions)
})
读取外部html2email配置
export type CliOptions = {
include: string | string[]
exclude: string | string[]
outDir: string
title: string
keepFolderStructure: boolean
}
CliOptions有5个配置项,虽然都可通命令行输入来覆盖默认值,这里我就直接读取外部package.json,html2email.json文件来获取当前配置项。
值得一提的是joycon真的好用,哈哈哈~ joycon npm
const JoyCon = require('joycon')
const joycon = new JoyCon({
packageKey: 'html2email'
})
const { path, data } = await joycon.load([
'package.json',
'html2email.json'
])
// console.log('path, data', path, data)
path: /Users/Autumn/package.json
data: { exclude: [ 'node_modules' ] }
genHtmlEmail实现
实现的目标是把当前目录下所有的.html文件的样式转为css内联样式的.html文件,可以分为以下几个步骤。
获取文件路径files
能躺平就躺平🐶,这里用fast-glob提供的方法
This package provides methods for traversing the file system and returning pathnames
遍历处理files并输出
这块就直接上代码吧.
export default async (config: CliOptions) => {
let {
include,
exclude,
outDir,
keepFolderStructure
} = config
if (typeof include === 'string') include = [include]
if (typeof exclude === 'string') exclude = [exclude]
const files = await fg(include.concat(exclude.map(p => `!${p}`)))
console.log('files', files)
return files.map(async (p: string) => {
// fs读取源文件
const abs = path.resolve(p)
const source = await fs.readFile(abs, 'utf-8')
// 转换成email支持的html css内联格式
try {
const parserHtml = await inlineCss(source, {
url: 'filePath'
})
const compName = path.basename(abs)
// fs输出最终文件
let targetDir = outDir? path.resolve(
outDir
) : path.dirname(abs)
let targetFile = compName
const folderStructureMiddlePath: string = keepFolderStructure
? getGlobPatternMatchPath(include as string[], path.dirname(p))
: ''
const target = path.resolve(
targetDir,
folderStructureMiddlePath,
targetFile
)
await fs.ensureDir(path.resolve(targetDir, folderStructureMiddlePath))
await fs.writeFile(target, parserHtml)
return {
compName,
content: parserHtml
}
} catch (error) {
console.log('error---- ', error)
}
})
}
另外getGlobPatternMatchPath方法的实现挺有意思的,感兴趣的小伙伴可以看看源码。
尾声
通过CLI命令行去批量处理后端同学之前100多个html邮件文件,瞬间处理完毕,下班打球⛹去~~~
上面👆🏻看似一番操作猛如虎,实际只实现了把css全部转成内联样式罢了,真要toemail还是得按各大邮件平台对css兼容性来写,The Ultimate Guide to CSS。
总结
在读了黄轶黄老师的一些文章之后,在经过这么一折腾感触颇深,按我以前愣头青,肯定就是直接一个个文件修改了(太菜了~~)....现在有了一种思维就是“解放双手,能偷懒就偷懒”,不然体力活真的顶不住;
特别是还原UI设计稿这块,起初要跟UI设计师订好组件规范,能抽成组件的就抽,能为后来省很多事,才有时间冲其他事情!
看完有收获,点赞评论支持一下把~
源代码