实践:实现一个markdown转图片的脚手架

12/18/2021 前端工程化Example脚手架
案例

# 功能

md2i —— md文档输出成图片

# 实操

# 安装模板

  1. 去官网caz (opens new window)可以看到文档,创建一个node模板,安装命令npm install -g caz
  2. 之后创建一个项目caz nm md2i
√ Project name ... md2i
√ Project version ... 0.1.0
√ Project description ... Awesome node modules.
√ Project author name ... csf
√ Project author email ... shuangfeng1993@163.com
# 必须填
√ Project author url ... http://zcegg.com
√ Project license » MIT
√ GitHub username or organization ... zce
# 可以选择,cli项目,还有生成说明文档,示例和测试
√ Choose the features you need » CLI Program
# 是否安装依赖
√ Install dependencies ... yes
# 用什么方式安装
√ Package manager » npm

Getting Started:
  $ cd md2i
  $ npm test

Happy hacking :)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  1. npm link

# 对cli.js进行分析修改

  1. md2i --help可以看到提示,那这些是通过bin/cli.js配置的,我们处理命令行参数,可以用 cac (opens new window),也可以用commander
// 处理命令行参数
cli
  .command('<input>', 'Sample cli program')
  .option('--host <host>', 'Sample options')
  .example(`  $ md2i w --host zce.me`)
  .action((input, options) => {
    console.log(md2I(input, options))
  })

cli.help().version(version).parse()
1
2
3
4
5
6
7
8
9
10
  1. 这里对整个cli.js做一个注释
#!/usr/bin/env node
// 在cli.js中一般只处理命令行参数,不写业务逻辑

// 引入处理命令行参数的包
const cac = require('cac')
// 这个..和../ 是一个作用,都是上一个目录,找上一个目录会先找有没有.js文件,没有就找package.json文件中的main字段
// main后面的值是lib/index.js,所以这里引用的文件就是lib/index.js
// md2I是一个函数,这里跳到调用的位置
const md2I = require('..')
const { name, version } = require('../package')

// Unified error handling
/* istanbul ignore next */
// 全局错误处理
const onError = err => {
  console.error(err.message)
  process.exit(1)
}

process.on('uncaughtException', onError)
process.on('unhandledRejection', onError)

const cli = cac(name)

// TODO: Implement module cli
// 处理命令行参数
cli
  // 表示我们要配置的命令,input命令,配置了之后必须要赋值,否则报错
  .command('<input>', 'Sample cli program')
  // 配置 --key value 的参数,如果想要配置多个就多调用几个option
  .option('--host <host>', 'Sample options')
  .example('  $ md2i w --host zce.me')
  .action((input, options) => {
    // 函数调用
    /**
     * 如果命令行输入 md2i foo --host abc
     * 那么这里输出 foo { '--': [], host: 'abc' }
     * input对应foo,options对应对象,格式是cac定义好的
     * 这里input是一个必传的参数
     */
    // console.log(input, options)
    console.log(md2I(input, options))
  })

cli.help().version(version).parse()

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

# 对index.js进行编写

# 目标

我们的目标是输入命令md2i changelog.md --output 1.png --width 600,就可以让changelog的md文件转化成宽度是600的png文件。下面我们对index.js进行处理

// bin/cli.js
// 把里面的input改成filename
cli
  .command('<filename>', 'Sample cli program')
  .option('--host <host>', 'Sample options')
  .example('  $ md2i w --host zce.me')
  .action((filename, options) => {
    console.log(md2I(filename, options))
  })

// lib/index.js  
module.exports = (filename, { output, width }) => {
    // 1 我们就按路径找到 filename 对应的 md 文件
    // 2 读取它里面的内容然后转为 html结构
    // 3 将html想办法处理成图片输出到output对应的路径,同时将宽度设置为用户传入的 width
    console.log(filename, output, width)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 1.按路径找到 filename 对应的 md 文件

知识点一:

console.log(__dirname) // index.js所在目录
console.log(process.cwd()) // 项目所在目录
1
2

知识点二:
我们需要当前路径的绝对路径,要进行转化

// cwd() => patj.join resolve
// 下面两个都产出相同的绝对路径
// E:\professer\md2i\changelog.md
// 下面两个参数是有区别的,简单说join更像是两个参数的拼接,resolve如果只传入一个参数,那么会自动将他处理为绝对路径【默认在前面不上cwd目录】
console.log(path.join(process.cwd(), filename))
console.log(path.resolve(filename))
console.log(path.resolve(process.cwd(), filename))
// E:\professer\md2i\changelog.md
1
2
3
4
5
6
7
8

知识点三:
读取绝对路径文件所对应的文件内容

const abspath = path.resolve(filename)
  const contents = fs.readFileSync(abspath, 'utf-8')
console.log(contents.toString())
1
2
3

知识点四:
判断文件是否存在,存在返回true,不存在返回false

console.log(fs.existsSync(abspath))
1

知识点五:
判断文件是不是一个文件而不是文件夹

const stat = fs.statSync(abspath)
stat.isDirectory() // 这个是true表示是文件夹,false是文件
1
2

而且我们还需要对路径是否存在和是不是一个文件进行判断,整体代码是这样

  // 1 依据传入的文件名拼接处具体的md文件路径
  // 1.1 传入的是md的文件名,我们需要的当前文件所在的绝对路径 cwd() => patj.join resolve
  // 1.2.1 判断当前的filename对应的内容是否真实存在,如果存在还要判断是否是一个 md 文件
  const abspath = path.resolve(filename)
  if (!fs.existsSync(abspath)) {
    // 说明文件是不存在的
    throw new Error('filename对应的文件是不存在的')
  }
  // 1.2.2 如果当前路径对应的目标存在这么直接判断是否为一个文件
  // statSync 接收一个文件路径返回一个对象,这个对象就涵盖了当前目标的所有信息
  const stat = fs.statSync(abspath)
  if (stat.isDirectory()) {
    // 此条件成立说明 filename是存在的,但是并不是文件名称,而是一个文件夹名称
    throw new Error('给定路径是一个文件夹')
  }

  // 1.3 读取绝对路径文件所对应的文件内容 ==> // 编码方式是utf-8
  const contents = fs.readFileSync(abspath, 'utf-8')
  console.log(contents.toString())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 2. 将读取出来的 md 格式处理为 html 结构

  • 安装npm i marked
  • 引入使用
const marked = require('marked')
const ret = marked(contents)
console.log(ret)
1
2
3

可以看到已经转化的结果

<!--转化前-->
# Changelog

## [0.1.0] - 2020-09-29

- Initial release
.... 

// -------------------------------
<!--转化后-->
<!--ret是一个html结构 —— 标签结构,不是网页-->
<h1 id="changelog">Changelog</h1>
<h2 id="010---2020-09-29">[0.1.0] - 2020-09-29</h2>
<ul>
<li>Initial release</li>
</ul>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 3. 将html展示内容处理为图片,然后输出

(无头浏览器,后台启动了,但是前台是看不见的) ,使用puppeteer模块,因为这个是二进制文件,99%会失败,网速有要求,可能会安装失败。建议是切到npm,提高网速去下载。

这个安装了,真的比较慢

  • 安装npm i puppeteer
  • 引入使用
const puppeteer = require('puppeteer')
// 里面用await,外面需要添加async
module.exports = async (filename, { output, width = 800 }) => {
    ...
    const browser = await puppeteer.launch() // 启动一个浏览器
    const page = await browser.newPage() // 创建了一个界面
    await page.setContent(ret) // 在界面中加载内容
    await page.screenshot({ path: 'example.png' }) // 截屏保存成一张图片,路径自己定义,默认是path
    await browser.close() // 完成关闭浏览器
}
1
2
3
4
5
6
7
8
9
10
  • 命令行md2i changelog.md,可以看到本地路径真的生成了一张example.png的图片

image

基本功能这里就完成了

# 4. 添加一些样式

可以使用模板引擎,这里简单的就用字符串拼接简单实现一下

const ret = marked(contents)
const temp = `<!DOCTYPE html>
  <html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
      h1 {
        color: red;
      }
    </style>
  </head>
  <body>
    ${ret}
  </body>
  </html>`
const html = temp.replace('${ret}', ret)
// console.log(html)
// 3 将html展示内容处理为图片,然后输出 (无头浏览器,后台启动了,但是前台是看不见的) puppeteer
// 涉及二进制文件去npm官网下载 npm源
const browser = await puppeteer.launch() // 启动一个浏览器
const page = await browser.newPage() // 创建了一个界面
await page.setContent(html) // 在界面中加载内容
await page.screenshot({ path: 'example.png' }) // 截屏保存成一张图片,路径自己定义,默认是path
await browser.close() // 完成关闭浏览器
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

可以看到文字变红了

image

也可以下载一个github-markdown-css然后用link的方式让html用导入,就可以有样式了。

更新时间: 2021-12-18 20:56