体力活?搭建自己的CLI命令工具,解放双手🙌🏻

2,255 阅读6分钟

最终效果

toemail.gif 源代码

背景

缘起

image.png
据知情人介绍,后端的发送邮件的服务是后端发送一个.html文件(后端写的)来实现,由于不同的邮件平台,对这个html的解析是不尽相同的,样式频繁出现不适配的情况。
好了! 后端接锅就不住了,改样式兼容的东西自然落到了前端头子身上,前端头子哪有这个空?这么大个锅又自然地落在了“前端切图仔”身上。

心死

适配html邮件第一件事就是:把css都写成内联样式!!

与普通 HTML 页面开发一样,HTML 邮件依旧离不开 CSS,HTML 邮件并不支持外部的 style 文件,head 标签极有可能被删除,所以不要试图在 head 标签内写 style 标签。 那么在 body 内写 style 标签是不是就保险了呢?并不是!典型的就是 Gmail 邮箱,会把 HTML 邮件内所有 style 标签删除,这就意味着只有內联 style 属性内的 CSS 是唯一可靠的样式信息。

image.png
好家伙,100多个这样的html文件,改完我直接羽化登仙~,按一个文件8min算吧(还不包括跑脚本测试的时间),这波操作下去,真体力活呀。

不干体力活

不干体力活,“切图仔”也是有尊严的。于是转念一想,既然100多个文件要做的事情一样,那先一个CLI命令行批量去处理不就完了? 目标:执行html2email gen,自动把html转为邮件平台适配的样式

场景

基于html邮件,我们看一下css内联样式的一般场景

  • 发送邮件
  • 在第三方网站中嵌入HTML
  • 从其他编辑器拷贝编辑好的文章发布到微信、今日头条等自媒体

在以上场景使用行内样式的兼容性要高很多,也可以保持原样式不变,加载速度也更快。目前的解决方案有 juiceinline-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就可以正常使用了
image.png

实现过程

新建了命令行项目之后,接下来是往项目里堆代码了,分为两个步骤

  • 处理识别用户输入的命令行
  • 执行html转为html邮件的方法(这里只是将样式转成内联样式!!)

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文件,可以分为以下几个步骤。

  • 遍历目录下的所有文件,找出.html文件得files数组
  • 遍历files
    • fs读取源文件
    • inline-css转换成email支持的html css内联样式
    • 输出最终转换后的文件

获取文件路径files

能躺平就躺平🐶,这里用fast-glob提供的方法

This package provides methods for traversing the file system and returning pathnames

输出就好得到以下数组
image.png

遍历处理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
image.png

总结

在读了黄轶黄老师的一些文章之后,在经过这么一折腾感触颇深,按我以前愣头青,肯定就是直接一个个文件修改了(太菜了~~)....现在有了一种思维就是“解放双手,能偷懒就偷懒”,不然体力活真的顶不住;
特别是还原UI设计稿这块,起初要跟UI设计师订好组件规范,能抽成组件的就抽,能为后来省很多事,才有时间冲其他事情!

看完有收获,点赞评论支持一下把~
源代码

加个后端的标签,万一帮到他们了呢,哈哈哈

参考

HTML 邮件兼容问题与解决方案
将css转换为行内样式的方案css-inline
vuese