监听 Markdown 文件并热更新 Next.js 页面
Next.js 提供了 Fast-Refresh 能力,它可以为您对 React 组件所做的编辑提供即时反馈。
但是,当你通过 Markdown 文件提供网站内容时,由于 Markdown 不是 React 组件,热更新将失效。
怎么做
解决该问题可从以下几方面思考:
- 服务器如何监控文件更新
- 服务器如何通知浏览器
- 浏览器如何更新页面
- 如何拿到最新的 Markdown 内容
- 如何与 Next.js 开发服务器一起启动
监控文件更新
约定: markdown 文件存放在 Next.js 项目根目录下的
_contents/
中
通过 node:fs.watch
模块递归的监控 _contents
目录,当文件发生变更,触发 listener 执行。
新建文件 scripts/watch.js
监控 _contents
目录。
const { watch } = require('node:fs');
function main(){
watch(process.cwd() + '/_contents', { recursive: true }, (eventType, filename) => {
console.log(eventType, filename)
});
}
通知浏览器
服务端通过 WebSocket 与浏览器建立连接,当开发服务器发现文件变更后,通过 WS 通知浏览器更新页面。
浏览器需要知道被更新的文件与当前页面所在路由是否有关,因此,服务端发送给浏览器的消息应至少包含当前
更新文件对应的页面路由。
WebSocket
ws
是一个简单易用、速度极快且经过全面测试的 WebSocket 客户端和服务器实现。通过 ws
启动 WebSocket 服务器。
const { watch } = require('node:fs');
const { WebSocketServer } = require('ws')
function main() {
const wss = new WebSocketServer({ port: 80 })
wss.on('connection', (ws, req) => {
watch(process.cwd() + '/_contents', { recursive: true }, (eventType, filename) => {
const path = filename.replace(/\.md/, '/')
ws.send(JSON.stringify({ event: 'markdown-changed', path }))
})
})
}
浏览器连接服务器
新建一个 HotLoad
组件,负责监听来自服务端的消息,并热实现页面更新。组件满足以下要求:
- 通过单例模式维护一个与 WebSocekt Server 的连接
- 监听到服务端消息后,判断当前页面路由是否与变更文件有关,无关则忽略
- 服务端消息可能会密集发送,需要在加载新版本内容时做防抖处理
- 加载 Markdown 文件并完成更新
- 该组件仅在
开发模式
下工作
import { useRouter } from "next/router"
import { useEffect } from "react"
interface Instance {
ws: WebSocket
timer: any
}
let instance: Instance = {
ws: null as any,
timer: null as any
}
function getInstance() {
if (instance.ws === null) {
instance.ws = new WebSocket('ws://localhost')
}
return instance
}
function _HotLoad({ setPost, params }: any) {
const { asPath } = useRouter()
useEffect(() => {
const instance = getInstance()
instance.ws.onmessage = async (res: any) => {
const data = JSON.parse(res.data)
if (data.event === 'markdown-changed') {
if (data.path === asPath) {
const post = await getPreviewData(params)
setPost(post)
}
}
}
return () => {
instance.ws.CONNECTING && instance.ws.close(4001, asPath)
}
}, [])
return null
}
export function getPreviewData(params: {id:string[]}) {
if (instance.timer) {
clearTimeout(instance.timer)
}
return new Promise((resolve) => {
instance.timer = setTimeout(async () => {
const res = await fetch('http://localhost:3000/api/preview/', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(params)
})
resolve(res.json())
}, 200)
})
}
let core = ({ setPost, params }: any)=>null
if(process.env.NODE_ENV === 'development'){
console.log('development hot load');
core = _HotLoad
}
export const HotLoad = core
数据预览 API
创建数据预览 API,读取 Markdown 文件内容,并编译为页面渲染使用的格式。这里的结果
应与 [...id].tsx
页面中 getStaticProps()
方法返回的页面数据结构完全一致,相关
逻辑可直接复用。
新建 API 文件 pages/api/preview.ts
,
import type { NextApiRequest, NextApiResponse } from 'next'
import { getPostData } from '../../lib/posts'
type Data = {
name: string
}
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
if (process.env.NODE_ENV === 'development') {
const params = req.body
const post = await getPostData(['posts', ...params.id])
return res.status(200).json(post)
} else {
return res.status(200)
}
}
更新页面
页面 pages/[...id].tsx
中引入 HotLoad
组件,并传递 setPostData()
及 params
给 HotLoad
组件。
...
import { HotLoad } from '../../components/hot-load'
const Post = ({ params, post, prev, next }: Params) => {
const [postData, setPostData] = useState(post)
useEffect(()=>{
setPostData(post)
},[post])
return (
<Layout>
<Head>
<title>{postData.title} - Gauliang</title>
</Head>
<PostContent post={postData} prev={prev} next={next} />
<BackToTop />
<HotLoad setPost={setPostData} params={params} />
</Layout>
)
}
export async function getStaticProps({ params }: Params) {
return {
props: {
params,
post:await getPostData(['posts', ...params.id])
}
}
}
export async function getStaticPaths() {
const paths = getAllPostIdByType()
return {
paths,
fallback: false
}
}
export default Post
启动脚本
更新 package.json
的 dev
脚本:
"scripts": {
"dev": "node scripts/watch.js & \n next dev"
},
总结
上述内容,整体概述了大致的实现逻辑。具体项目落地时,还需考虑一些细节信息,
如:文件更新时希望能够在命令行提示更的文件名、针对个性化的路由信息调整文件与路由的匹配逻辑等。
Next.js 博客版原文:https://gauliang.github.io/blogs/2022/watch-markdown-files-and-hot-load-the-nextjs-page/
监听 Markdown 文件并热更新 Next.js 页面的更多相关文章
- ORACLE清理、截断监听日志文件(listener.log)
在ORACLE数据库中,如果不对监听日志文件(listener.log)进行截断,那么监听日志文件(listener.log)会变得越来越大,想必不少人听说过关于"LISTENER.LOG日 ...
- ORACLE 监听日志文件太大停止写监听日志引起数据库连接不上问题
生产库监听日志文件太大(达到4G多),发现oracle停止写监听日志,检查参数log_file,log_directory,log_status 均正常,数据库运行也正常. 经确认确实为监听日志过大引 ...
- 文件下载Controller,文件夹内容监听,文件上传,运行程序通过url实现文件下载
文件下载Controller @RequestMapping("/fileDownLoad") public ResponseEntity<byte[]> fileDo ...
- Oracle数据库运维:要对监听日志文件(listener.log)进行定期清理,如果不定期清理,会遇到下面一些麻烦
原文链接: http://www.lookdaima.com/WebForms/WebPages/Blanks/Pm/Docs/DocItemDetail.aspx?EmPreviewTypeV=2& ...
- 监听Documents文件夹内文件发生改变
// 当Documents内文件发生改变时,启动计时器,每秒计算一次大小,当大小不发生改变时说明传输完毕,就开始刷新. @property (nonatomic, strong) NSTimer *t ...
- Xlua文件在热更新中调用方法
Xlua文件在热更新中调用方法 public class news : MonoBehaviour { LuaEnv luaEnv;//定义Lua初始变量 void Awake() { luaEnv ...
- Vue 事件监听实现导航栏吸顶效果(页面滚动后定位)
Vue 事件监听实现导航栏吸顶效果(页面滚动后定位) Howie126313 关注 2017.11.19 15:05* 字数 100 阅读 3154评论 0喜欢 0 所说的吸顶效果就是在页面没有滑动之 ...
- Webpack多入口文件、热更新等体验
Webpack现今流行的前端打包工具,今儿本人也来分享下自己学习体验. 一.html-webpack-plugin 实现html模板文件的解析与生成 在plugins加入HtmlWebpackPlug ...
- arcgis engine 监听element的添加、更新和删除事件(使用IGraphicsContainerEvents)
IGraphicsContainerEvents Interface 如何监听 element事件? 如,当我们在Mapcontrol上添加.删除.更新了一个Element后,如何捕捉到这个事件? ...
随机推荐
- NodeJs学习日报day8——接口编写
今天看了黑马NodeJs中关于接口编写以及跨域问题的视频
- 2021.12.06 P2508 [HAOI2008]圆上的整点(数论+ π )
2021.12.06 P2508 [HAOI2008]圆上的整点(数论+ \(\pi\) ) https://www.luogu.com.cn/problem/P2508 题意: 求一个给定的圆 \( ...
- python源码方式安装后如何卸载
可以重新源码安装,此时需要记录安装文件细节,可通过--record XX来记录,如: python setup.py install --record setup.log 这时所有的安装细节都写到lo ...
- macOS 安装 Nebula Graph 看这篇就够了
本文首发于 Nebula Graph Community 公众号 背景 刚学习图数据的内容,当前网上充斥大量的安装文档,参差不齐,部署起来令人十分头疼. 现整理一份比较完整的安装文档,供大家学习参考, ...
- python学习-Day36
目录 进程 进程间通信 队列(multiprocess.Queue) 概念 Queue的实例q具有以下方法: 消息队列 生产者消费者模型 实现生产者消费者模型三要素: 线程理论 线程概念 线程存在的意 ...
- python 本地配置文件库 Dynaconf 简介
[前言] 在项目中经常会遇到以下几种需要用到配置文件的场景: 相同的配置参数用在不同的代码中,如果需要调整,则需要手动将各个使用到的地方都相应调整. 密码等信息不想硬编码在项目文件中. 配置文件的格式 ...
- opencv如何在jupyter notebook中显示图片
方法一: from matplotlib import pyplot as plt import numpy as np import cv2 img = cv2.imread('img.jpg') ...
- 『现学现忘』Git基础 — 20、Git中忽略文件补充
目录 1.忽略文件常遇到的问题 2.忽略文件配置优先级 3.忽略已跟踪文件的改动(本机使用) 4.autocrlf和safecrlf参数说明 (1)提出问题 (2)autocrlf说明 (3)safe ...
- 『现学现忘』Git基础 — 23、Git中的撤销操作
目录 1.撤销操作说明 2.撤销工作区中文件的修改 3.撤销暂存区中文件的修改 4.总结 1.撤销操作说明 我们在使用Git版本管理时,往往需要撤销某些操作.比如说我们想将某个修改后的文件撤销到上一个 ...
- 2┃音视频直播系统之浏览器中通过 WebRTC 拍照片加滤镜并保存
一.拍照原理 好多人小时候应该都学过,在几张空白的纸上画同一个物体,并让物体之间稍有一些变化,然后连续快速地翻动这几张纸,它就形成了一个小动画,音视频播放器就是利用这样的原理来播放音视频文件的 播放器 ...