Appearance
实践:实现一个markdown转图片的脚手架 案例
功能
md2i —— md文档输出成图片
实操
安装模板
- 去官网caz可以看到文档,创建一个node模板,安装命令
npm install -g caz
- 之后创建一个项目
caz nm md2i
bash
√ 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 :)
- npm link
对cli.js进行分析修改
md2i --help
可以看到提示,那这些是通过bin/cli.js
配置的,我们处理命令行参数,可以用 cac,也可以用commander
js
// 处理命令行参数
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()
- 这里对整个cli.js做一个注释
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()
对index.js进行编写
目标
我们的目标是输入命令md2i changelog.md --output 1.png --width 600
,就可以让changelog的md文件转化成宽度是600的png文件。下面我们对index.js
进行处理
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.按路径找到 filename 对应的 md 文件
知识点一:
jsconsole.log(__dirname) // index.js所在目录 console.log(process.cwd()) // 项目所在目录
知识点二:
我们需要当前路径的绝对路径,要进行转化js// 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
知识点三:
读取绝对路径文件所对应的文件内容jsconst abspath = path.resolve(filename) const contents = fs.readFileSync(abspath, 'utf-8') console.log(contents.toString())
知识点四:
判断文件是否存在,存在返回true,不存在返回falsejsconsole.log(fs.existsSync(abspath))
知识点五:
判断文件是不是一个文件而不是文件夹jsconst stat = fs.statSync(abspath) stat.isDirectory() // 这个是true表示是文件夹,false是文件
而且我们还需要对路径是否存在和是不是一个文件进行判断,整体代码是这样
js
// 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())
2. 将读取出来的 md 格式处理为 html 结构
- 安装
npm i marked
- 引入使用
js
const marked = require('marked')
const ret = marked(contents)
console.log(ret)
可以看到已经转化的结果
html
<!--转化前-->
# 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>
3. 将html展示内容处理为图片,然后输出
(无头浏览器,后台启动了,但是前台是看不见的) ,使用puppeteer模块,因为这个是二进制文件,99%会失败,网速有要求,可能会安装失败。建议是切到npm,提高网速去下载。
这个安装了,真的比较慢
- 安装
npm i puppeteer
- 引入使用
js
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() // 完成关闭浏览器
}
- 命令行
md2i changelog.md
,可以看到本地路径真的生成了一张example.png
的图片
基本功能这里就完成了
4. 添加一些样式
可以使用模板引擎,这里简单的就用字符串拼接简单实现一下
js
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() // 完成关闭浏览器
可以看到文字变红了
也可以下载一个github-markdown-css
然后用link的方式让html用导入,就可以有样式了。