Nodejs 第二十三章 Markdown 转 html
什么是markdown语法
Markdown是一种轻量级标记语言,它允许人们使用易读易写
的纯文本格式编写文档,然后转换成有效的XHTML(或者HTML)文档。由John Gruber和Aaron Swartz共同创建于2004年,Markdown的目的是实现「易读易写」,其语法灵感来源于电子邮件的格式化文本
markdown语法的必要性
- 易于学习和使用:Markdown的语法简单直观,常用的标记符号不超过十个,这使得即使是Markdown的初学者也能快速上手并使用它来进行日常的写作任务。
- 纯文本:Markdown文件是纯文本,因此可以使用任何文本编辑器打开和编辑,同时也易于版本控制。
- 转换灵活:Markdown文本可以通过工具轻松转换为HTML、PDF、EPUB等多种格式,便于发布与分享。
-
上面三个特点是
markdown
语法的巨大优势,基本上涵盖了写文章最必要的几个要素- 包括说我目前写这篇文章,所采用的也是markdown语法。在通过
Typora
软件进行转化的时候,也非常的方便。
- 包括说我目前写这篇文章,所采用的也是markdown语法。在通过
markdown用途
- Markdown的简洁与灵活使其成为程序员、作家和许多需要编写丰富文档的用户的首选语言。它特别适合写作和快速记录注释,也被广泛用于编写README文件、科学文档、博客、论坛和在线讨论
- 我们看到的各种技术博客文档,基本上都是采用markdown语法进行的
使用到的第三方库
虽然市面上有很多已经集成好的markdown编辑器,但具备转化功能的免费软件其实还是较少的。为此,不妨我们自己来实现一次这个功能
第三方库概述
- 我们先对即将使用到的三个第三方库进行几个简要的了解
-
EJS (Embedded JavaScript Templating) :
- 功能:一个JavaScript模板引擎,允许开发者将JavaScript变量和函数嵌入到HTML中,用于生成动态内容。
- 优点:支持复杂的逻辑处理,如条件判断和循环,易于生成复杂的HTML结构。
-
Marked:
- 功能:一个Markdown解析器,用于将Markdown文本转换为HTML代码。
- 优点:快速、轻量且可配置,支持标准Markdown和GitHub Flavored Markdown(GFM)。
-
BrowserSync:
- 功能:一个开发工具,可同时在多个设备和浏览器上同步页面改动,并实时刷新查看效果。
- 优点:支持多设备实时预览,自动化页面刷新,提高响应式和跨设备测试的效率。
EJS语法
官网地址:EJS -- 嵌入式 JavaScript 模板引擎 | EJS 中文文档 (bootcss.com)
具体的使用方式从官网学习
- 数据绑定:EJS允许将服务器端的数据直接绑定到HTML标记中,从而在渲染页面时动态生成内容。
- 代码重用:通过使用EJS的
<% include %>
标签,可以重用HTML片段,如头部、尾部或导航栏,从而简化代码维护。 - 条件语句和循环:EJS支持JavaScript的控制语句,如
if
、else
、while
、for
等,使得在模板中处理逻辑成为可能。
1.纯脚本标签
<% code %>
里面可以写任意的 js,用于流程控制,无任何输出
<% alert('hello world') %> // 会执行弹框
2.输出经过 HTML 转义的内容
<%= value %>
可以是变量 <%= a ? b : c %>
也可以是表达式 <%= a + b %>
即变量如果包含 '<'、'>'、'&'等HTML字符,会被转义成字符实体,像< > &
因此用<%=
,最好保证里面内容不要有HTML字符
const text = '<p>猴赛雷</p>'
<h2><%= text %></h2>
// 输出 <p>猴赛雷</p> 插入 <h2> 标签中
3. 输出非转义的内容(原始内容)
<%- 富文本数据 %>
通常用于输出富文本,即 HTML内容 上面说到<%=
会转义HTML字符,那如果我们就是想输出一段HTML怎么办呢? <%-
不会解析HTML标签,也不会将字符转义后输出。像下例,就会直接把 <p>我来啦</p>
插入标签中
const content = '<p>标签</p>'
<h2><%- content %></h2>
4. 引入其他模版
<%- include('***文件路径') %>
将相对于模板路径中的模板片段包含进来。 用<%- include
指令而不是<% include
,为的是避免对输出的 HTML 代码做转义处理。
// 当前模版路径:./views/tmp.ejs
// 引入模版路径:./views/user/show.ejs
<ul>
<% users.forEach(function(user){ %>
<%- include('user/show', {user: user}); %>
<% }); %>
</ul>
5. 条件判断
<% if (condition1) { %>
...
<% } %>
<% if (condition1) { %>
...
<% } else if (condition2) { %>
...
<% } %>
// 举例
<% if (a && b) { %>
<p>可以直接放 html 内容</p>
<% } %>
<% if (a && b) { %>
<% console.log('也可以嵌套任意ejs模版语句') %>
<% } %>
6. 循环
//使用传统 for 循环遍历数组
//这种方式适合于需要索引的场景,因为可以直接使用变量 i 获取当前循环的索引
<%
// `target` 应该是一个数组或类数组对象。
// 循环从0开始,直到数组的长度。
for(var i = 0; i < target.length; i++){
%>
<%= i %> <%= target[i] %> <!-- 输出当前的索引 `i` 和对应的数组元素 `target[i]` -->
<% } %>
<%
// 使用for-in循环遍历对象 `jsArr` 的所有属性。
// 通常 `jsArr` 应该是一个对象,其中的属性是脚本文件的路径。
for(var i in jsArr) {
%>
<script type="text/javascript" src="<%= jsArr[i] %>" ref="preload"></script> <!-- 动态插入脚本标签,其src属性是由对象 `jsArr` 的属性值决定的 -->
<% } %>
//推荐方式
<%
// 使用for-of循环遍历数组 `cssArr`。
// `cssArr` 应该是包含CSS文件链接的数组。
// 这是一种简洁的方式来遍历数组的元素,不需要使用索引。
for(var css of cssArr) {
%>
<link rel="stylesheet" href="<%= css %>" /> <!-- 创建link标签,链接到CSS文件。属性href的值来自数组 `cssArr` 中的元素 -->
<% } %>
初始化模板
-
以下代码块为template.ejs,也就是后缀为ejs的文件
-
初始化模板 被
<%- XXX %>
到时候会转换成html代码
这里需要区分:
<%= %>
:这个语法用于输出转义后的HTML内容。当我们的数据可能包含HTML标签或特殊字符时,使用<%= %>
可以避免跨站脚本攻击(XSS),因为它会将字符如<
、>
、&
等转义成HTML实体。<%- %>
:这个语法用于输出原始HTML内容。当我们确定嵌入的内容是安全的,或者需要包括HTML标签时使用。在包含另一个模板时,使用<%- %>
可以确保模板的HTML标签不会被转义,从而正常地作为HTML元素被渲染。
-
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title %></title>
<link rel="stylesheet" href="./index.css">
</head>
<body>
<%- content %>
</body>
</html>
使用场景
- 动态网站开发,例如博客、电商平台,其中内容需要根据数据库的数据变化而变化。
- 生成电子邮件模板,根据用户的行为发送定制化的通知。
marked
Marked 是一个快速且灵活的Markdown解析器,将Markdown文档转换为HTML是它的主要功能。它支持标准的Markdown语法,并提供了扩展功能,如GFM(GitHub Flavored Markdown)
核心功能
- 高性能:Marked 的解析速度非常快,适合处理大量文档。
- 灵活性:支持自定义渲染器,允许开发者根据需要修改生成的HTML。
- 兼容性:支持多种Markdown语法,包括GFM和原始Markdown。
使用场景
- 在线文档和帮助系统,允许快速渲染Markdown为用户友好的HTML页面。
- 博客平台,用户写作的Markdown文章可以即时转换并显示。
使用方式
- 将md 转换成html
const marked = require('marked')
marked.parse(readme.toString()) //调用parse即可,其中readme就是要转化的markdown内容
browserSync
BrowserSync 是一个用于前端开发的工具,它可以同步多个设备和浏览器中的动作,例如滚动、点击和刷新
核心功能
- 实时重载:当文件被修改时,BrowserSync能自动刷新所有已连接的设备和浏览器,显示最新的内容。
- 设备同步:在多个设备上测试时,可以同步我们在一个设备上的操作到其他所有设备,如滚动、表单输入等。
- 易于集成:可以与大多数构建工具和预处理器集成,如Gulp、Webpack等。
使用场景
- 响应式网站开发,同时在多个设备和屏幕尺寸上预览网站,确保跨设备兼容性。
- 快速测试和调试,改善开发流程的效率,尤其是在涉及多页面和复杂交互的大型项目中
使用方式
-
创建browser 并且开启一个服务 设置根目录和 index.html 文件
- 能够快速启动服务,运行我们的HTML文件,且支持热更新等功能
const browserSync = require('browser-sync')
const openBrowser = () => {
const browser = browserSync.create()
browser.init({//初始化browser
server: {
baseDir: './',//根目录
index: 'index.html',//入口文件
}
})
return browser
}
- html代码有了 但是没有通用的markdown的通用css。这个就跳过好了(因为太长了),仁者见仁智者见智,根据自己的喜好设置自己喜欢的样式
markdown转HTML案例
前置基础框架建设
-
首先安装这三个库:
npm i ejs marked browser-sync
-
然后初始化package.json文件:
npm init -y
-
创建模板文件template.ejs
-
创建必要的index.js入口文件,markdown的index.css样式文件
建立模板
-
把我们在前面EJS语法中的初始化模板搬过来进行使用就OK了
- 其中title和content都是占位符,到时候会有内容将其替换
实现转化效果
-
通过ejs,我们就能实现将markdown转化为HTML的效果,接下来就让我们来梳理下流程吧!
-
首先我们需要获取到markdown的内容,这是第一步
-
有了基础的内容,我们就要把它往我们的模板身上套了。还记得我们前面的初始化模板吗?
一共有两个占位符,一个是title标题,一个是content内容
-
-
但要怎么套呢?
-
通过ejs的renderFile方法就能够做到这点,其中有三个参数
- 参数1:传递我们的模板
- 参数2:传递我们提前在模板中写好的占位符
- 参数3:回调函数返回的结果,分别是err(错误)和str(结果)
-
那这样就好办了呀,我们将template.ejs套进参数1模板里面,参数2设置好title和content,参数3拿到结果写入要将markdown转为HTML的文件就结束了
-
-
这里有几个点,在参数2中,title是字符串好说,但content就是markdown的内容了,在获取的时候,我们要先通过fs模块的读取文件拿到内容,然后通过marked第三方库将其转化为HTML的展示形态
- 最后在参数3的回调函数判断一下是否有发送错误,有则返回错误,没有就使用fs模块的
writeFileSync
写入功能,创建出新的index.html展示结果
- 最后在参数3的回调函数判断一下是否有发送错误,有则返回错误,没有就使用fs模块的
const ejs = require('ejs'); // 导入ejs库,用于渲染模板
const fs = require('node:fs'); // 导入fs模块,用于文件系统操作
const marked = require('marked'); // 导入marked库,用于将Markdown转换为HTML
const browserSync = require('browser-sync'); // 导入browser-sync库,用于实时预览和同步浏览器
const init = () => {
const md = fs.readFileSync('./test.md', 'utf-8'); // 读取test.md文件的内容
// 参数1:渲染对象,参数2:填充占位符内容,参数3:回调函数返回内容处理
ejs.renderFile('./template.ejs', {
// 拿到markdown文档的内容,进行转化为html
content: marked.parse(md),
title: " Nodejs 第二十三章 Markdown 转 html"
}, (err, str) => {
// 有报错返回报错,没有则正常写入内容到一个新的HTML文件中,这也是我们要做到的将markdown转为HTML
if (err) return err
fs.writeFileSync('index.html', str)
});
}
init()
- 通过打开生成的效果图,能看到是成功了的
热更新效果
-
利用第三方库browserSync起一个服务
- 在server选项中,我们设置了根目录,然后依据根目录的相对位置,设置了默认启动文件index.html
const browserSync = require('browser-sync')
const server = () => {
const browser = browserSync.create()
browser.init({
server: {
baseDir: './',
index: 'index.html',
}
})
return browser
}
- 然后在内容写入到index.html的时候调用这个
server
函数去起服务器
-
能够看到已经正常启动服务器了,但此时还没有热更新的功能,我们修改了markdown文档的内容,浏览器并没有跟着一起改变
- 我们通过fs模块的WatchFile进行监听markdown文档是否发生了变化,如果变化就重新调用服务。
- 但这个有一个问题,那就是当我们重新调用服务的时候,它会在原有基础上再开一个服务
fs.watchFile('test.md',(curr,prev) => {
if(curr.mtime !== prev.mtime) {
init()
}
})
-
但我们不希望连续开多个服务,而是重新刷新一下,那应该怎么做?
- 首先我们在编写server函数的时候,得到browserSync的创建方法时,不应该进行固定,而是声明成全局变量
//原有写法
const browser = browserSync.create();
//修改后的写法
let browser;//提前声明全局变量
browser = browserSync.create();
- 在init方法函数中,传入一个callback回调函数用来处理重新刷新
const init = (callback) => {
const md = fs.readFileSync('./test.md', 'utf-8'); // 读取test.md文件的内容
// 参数1:渲染对象,参数2:填充占位符内容,参数3:回调函数返回内容处理
ejs.renderFile('./template.ejs', {
// 拿到markdown文档的内容,进行转化为html
content: marked.parse(md),
title: " Nodejs 第二十三章 Markdown 转 html"
}, (err, str) => {
// 有报错返回报错,没有则正常写入内容到一个新的HTML文件中,这也是我们要做到的将markdown转为HTML
if (err) return err
fs.writeFileSync('index.html', str)
callback && callback()
});
}
fs.watchFile('test.md',(curr,prev) => {
if(curr.mtime !== prev.mtime) {
init(() =>{
browser.reload()
})
}
})
init(() => {
server()
})
- 然后再重新启动服务的时候,修改markdown文件,浏览器就会自己进行刷新,同时图标也显示了内容的更新状态
- 这样热更新的解决方法是很方便的,建立起全局变量。这样我们
browserSync.create()
和browser.reload()
指向的其实是同一个服务。那后者一旦reload
重新加载了,相当于重新加载了一开始的服务,因为此时他们是同一个体
彩蛋
-
这个BrowserSync我不知道大家还记不记得最前面介绍的时候,它的优点是什么?
- 支持多设备实时预览,自动化页面刷新
- 具体体现出来就是我们电脑滑动页面的时候,手机端也会跟着自动滑动到相对应的内容上。反之也是一样的
转载自:https://juejin.cn/post/7359510120248262683