Next.js 熱更新 Markdown 文件變更

来源:https://www.cnblogs.com/kelsen/archive/2022/06/24/watch-markdown-files-and-hot-load-the-nextjs-page.html
-Advertisement-
Play Games

Next.js 提供了 Fast-Refresh 能力,它可以為您對 React 組件所做的編輯提供即時反饋。 但是,當你通過 Markdown 文件提供網站內容時,由於 Markdown 不是 React 組件,熱更新將失效。 怎麼做 解決該問題可從以下幾方面思考: 伺服器如何監控文件更新 伺服器 ...


Next.js 提供了 Fast-Refresh 能力,它可以為您對 React 組件所做的編輯提供即時反饋。
但是,當你通過 Markdown 文件提供網站內容時,由於 Markdown 不是 React 組件,熱更新將失效。

怎麼做

解決該問題可從以下幾方面思考:

  1. 伺服器如何監控文件更新
  2. 伺服器如何通知瀏覽器
  3. 瀏覽器如何更新頁面
  4. 如何拿到最新的 Markdown 內容
  5. 如何與 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 組件,負責監聽來自服務端的消息,並熱實現頁面更新。組件滿足以下要求:

  1. 通過單例模式維護一個與 WebSocekt Server 的連接
  2. 監聽到服務端消息後,判斷當前頁面路由是否與變更文件有關,無關則忽略
  3. 服務端消息可能會密集發送,需要在載入新版本內容時做防抖處理
  4. 載入 Markdown 文件並完成更新
  5. 該組件僅在 開發模式 下工作
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()paramsHotLoad 組件。

...
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.jsondev 腳本:

"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/

識微見遠 格物致知
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 四、Nginx 4.1、概述 4.1.1、介紹 Nginx是一款輕量級的Web伺服器/反向代理伺服器/電子右鍵(IMAP/POP3)代理伺服器。其特點是占有記憶體少,併發能力強,事實上Nginx的併發能力在同類型的網頁伺服器中表現較好,中國大陸使用Nginx的網站有:百度、就京東、新浪、網易、騰訊、淘 ...
  • 鏡像下載、功能變數名稱解析、時間同步請點擊 阿裡雲開源鏡像站 CentOS 1.備份原來的源 在控制台輸入mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup備份原本的源 2、下載新的 CentOS-Bas ...
  • 鏡像下載、功能變數名稱解析、時間同步請點擊 阿裡雲開源鏡像站 一、環境要求 系統版本:CentOS7.x版本 硬體配置:記憶體2GB以上 cpu2核以上 硬碟大於30G 集群網路配置:集群中所有伺服器內網必須互通,並且需要訪問外網來拉取鏡像 禁用swap分區 二、k8s基礎環境操作: 1、關閉防火牆: [ro ...
  • 程式包編譯安裝的步驟: 源代碼-->預處理-->編譯-->彙編-->鏈接-->執行 多文件:文件中的代碼之間,很可能存在跨文件依賴關係 編譯源碼的項目工具 使用相關的項目管理工具可以大大減少編譯過程的複雜度 根據源碼類型來對這些工具進行分類: C、C++的源碼編譯:使用 make 項目管理器 con ...
  • 1、簡述 binlog 二進位日誌文件,這個文件記錄了MySQL所有的DML操作。通過binlog日誌我們可以做數據恢復,增量備份,主主複製和主從複製等等。 2、Docker中無法使用vim問題解決 https://blog.csdn.net/Tomwildboar/article/details/ ...
  • Android multiple back stacks導航 談談android中多棧導航的幾種實現. 什麼是multiple stacks 當用戶在app里切換頁面時, 會需要向後回退到上一個頁面, 頁面歷史被保存在一個棧里. 在Android里我們經常說"back stack". 有時候在app ...
  • 隨著人們對生活的儀式感的追求,移動設備、可穿戴設備、智能家居設備、車載信息娛樂系統也變得越來越流行。在這些應用上,滑鼠、鍵盤這樣的交互方式不再便捷,而語音作為人類之間最自然的交流方式,語音識別技術儼然已成為各大應用的“標配”。語音識別場景應用十分廣泛,如語音輸入法、語音搜索、實時字幕、游戲娛樂、社交 ...
  • 首先網上很多教程是關於使用swift SMAMB2包的,大部分都是 pod "SMAMB2" 這種方式還是非常簡單的,如果僅僅是這樣還不至於專門寫一篇文章來紀念他。問題就出在我的項目需要用到SMB協議去讀取文件,但是並不是直接讀取! 通俗一點來講就是我的用法和 SMAMB2包提供的用法不同! 那就意 ...
一周排行
    -Advertisement-
    Play Games
  • 概述:本文代碼示例演示瞭如何在WPF中使用LiveCharts庫創建動態條形圖。通過創建數據模型、ViewModel和在XAML中使用`CartesianChart`控制項,你可以輕鬆實現圖表的數據綁定和動態更新。我將通過清晰的步驟指南包括詳細的中文註釋,幫助你快速理解並應用這一功能。 先上效果: 在 ...
  • openGauss(GaussDB ) openGauss是一款全面友好開放,攜手伙伴共同打造的企業級開源關係型資料庫。openGauss採用木蘭寬鬆許可證v2發行,提供面向多核架構的極致性能、全鏈路的業務、數據安全、基於AI的調優和高效運維的能力。openGauss深度融合華為在資料庫領域多年的研 ...
  • openGauss(GaussDB ) openGauss是一款全面友好開放,攜手伙伴共同打造的企業級開源關係型資料庫。openGauss採用木蘭寬鬆許可證v2發行,提供面向多核架構的極致性能、全鏈路的業務、數據安全、基於AI的調優和高效運維的能力。openGauss深度融合華為在資料庫領域多年的研 ...
  • 概述:本示例演示了在WPF應用程式中實現多語言支持的詳細步驟。通過資源字典和數據綁定,以及使用語言管理器類,應用程式能夠在運行時動態切換語言。這種方法使得多語言支持更加靈活,便於維護,同時提供清晰的代碼結構。 在WPF中實現多語言的一種常見方法是使用資源字典和數據綁定。以下是一個詳細的步驟和示例源代 ...
  • 描述(做一個簡單的記錄): 事件(event)的本質是一個委托;(聲明一個事件: public event TestDelegate eventTest;) 委托(delegate)可以理解為一個符合某種簽名的方法類型;比如:TestDelegate委托的返回數據類型為string,參數為 int和 ...
  • 1、AOT適合場景 Aot適合工具類型的項目使用,優點禁止反編 ,第一次啟動快,業務型項目或者反射多的項目不適合用AOT AOT更新記錄: 實實在在經過實踐的AOT ORM 5.1.4.117 +支持AOT 5.1.4.123 +支持CodeFirst和非同步方法 5.1.4.129-preview1 ...
  • 總說周知,UWP 是運行在沙盒裡面的,所有許可權都有嚴格限制,和沙盒外交互也需要特殊的通道,所以從根本杜絕了 UWP 毒瘤的存在。但是實際上 UWP 只是一個應用模型,本身是沒有什麼許可權管理的,許可權管理全靠 App Container 沙盒控制,如果我們脫離了這個沙盒,UWP 就會放飛自我了。那麼有沒... ...
  • 目錄條款17:讓介面容易被正確使用,不易被誤用(Make interfaces easy to use correctly and hard to use incorrectly)限制類型和值規定能做和不能做的事提供行為一致的介面條款19:設計class猶如設計type(Treat class de ...
  • title: 從零開始:Django項目的創建與配置指南 date: 2024/5/2 18:29:33 updated: 2024/5/2 18:29:33 categories: 後端開發 tags: Django WebDev Python ORM Security Deployment Op ...
  • 1、BOM對象 BOM:Broswer object model,即瀏覽器提供我們開發者在javascript用於操作瀏覽器的對象。 1.1、window對象 視窗方法 // BOM Browser object model 瀏覽器對象模型 // js中最大的一個對象.整個瀏覽器視窗出現的所有東西都 ...