用到的全部總結在這里,不定期更新
鏈接 node一本通 包括: express path fs/ process/ os/ http/ mysql/mongoose/ express-jwt/jsonwebtoken/ dotenv/ multer/ swagger/ cors/ nodemon (docker篇有) 常用模塊 內置 fs 文件系統操作(讀寫、重命名等) path 路徑處理(拼接、解析、擴展名等) http 創建 HTTP 服務或客戶端 os 獲取操作系統信息(CPU、內存等) events 事件驅動機制(監聽、觸發事件). stream 處理流式數據(文件讀寫、管道等). crypto 加密、哈希、簽名等安全相關功能. child_process 創建子進程、執行命令行任務. util 工具函數(繼承、promisify 等). 第三方: express Web 框架,快速構建 API 服務 axios 發起 HTTP 請求(支持 Promise) dotenv 加載 .env 環境變量配置 chalk 終端輸出彩色文本 commander 構建命令行工具 nodemon 自動重啟 Node 服務(開發利器) jsonwebtoken JWT 鑒權與令牌生成 mongoose MongoDB ODM,操作數據庫更方便 cors 處理跨域請求 body-parser 解析請求體(Express 中常用). multer 處理文件上傳 socket.io 實現 WebSocket 實時通信
目錄 URL / URLSearchParams/ qs axios 發起 HTTP 請求(支持 Promise) body-parser 解析請求體(Express 中常用) commander inquirer 詢問 搭配commander chokidar open chalk socket.io 插件機制
URL / URLSearchParams/ qs
URL / URLSearchParams 是node/瀏覽器都支持 qs 第三方庫,用于處理復雜query(嵌套對象/數組),放入瀏覽器需要打包(webpack/vite)npm i qs
const myURL = new URL ( 'https://example.com:8080/path?a=123&b=456#hash' )
console. log ( myURL. protocol)
console. log ( myURL. hostname)
console. log ( myURL. port)
console. log ( myURL. pathname)
console. log ( myURL. hash)
console. log ( url. search) ; const params = url. searchParams;
myURL. searchParams. set ( 'a' , '100' )
console. log ( myURL. toString ( ) )
for ( const [ key, value] of myURL. searchParams) { console. log ( key, value)
}
const url = 'http://www.example.com/a/index.html?a=123&b=456' ;
const regex = / ^(https?):\/\/([^\/?#]+)(\/[^?#]*)?(\?[^#]*)?(#.*)?$ / ;
const params = new URLSearchParams ( '?name=Alice&age=30' ) ;
console. log ( params. get ( 'name' ) ) ;
console. log ( params. has ( 'age' ) ) ;
params. set ( 'age' , '31' ) ;
params. append ( 'hobby' , 'reading' ) ;
console. log ( params. toString ( ) ) ;
const qs = require ( 'qs' )
const str = qs. stringify ( { a: 1 , b: [ 2 , 3 ] , c: { d: 4 }
} )
console. log ( str)
const obj = qs. parse ( 'a=1&b[0]=2&b[1]=3&c[d]=4' )
console. log ( obj)
app. get ( '/search' , ( req, res ) => { const filter = require ( 'qs' ) . parse ( req. query) console. log ( filter)
} )
axios 發起 HTTP 請求(支持 Promise)
在nodejs 和 js 用法基本一樣,但有細節差異
項目 瀏覽器環境 Node.js 環境 請求底層 使用 XMLHttpRequest
或 fetch
使用 Node 的 http
或 https
模塊 Cookie 自動攜帶 默認攜帶當前域名下的 Cookie 需要手動設置 withCredentials
或 headers
CORS 跨域限制 受瀏覽器安全策略限制 不受限制,可自由請求任何地址 文件上傳 使用 FormData
需使用 form-data
模塊或 Buffer 瀏覽器特性支持 可顯示加載動畫、進度條等 需手動實現進度監聽邏輯
應用場景 調用第三方 API:比如天氣、支付、地圖、AI 接口等 服務間通信:微服務架構中,一個服務調用另一個服務的接口 數據同步:定時任務拉取遠程數據,寫入數據庫 代理轉發請求:結合 Express 做 API 網關或中間層 上傳/下載文件:配合 fs 模塊處理文件流 自動化腳本:比如爬蟲、批量提交數據、接口測試等
import axios from 'axios' ;
axios. get ( '/api/user' ) . then ( res => console. log ( res. data) ) . catch ( err => console. error ( err) ) ; const axios = require ( 'axios' ) ;
axios. get ( 'https://api.example.com/user' ) . then ( res => console. log ( res. data) ) . catch ( err => console. error ( err) ) ; axios. get ( 'https://api.example.com/data' , { withCredentials: true
} ) ; axios. get ( 'https://api.example.com/data' , { headers: { Cookie: 'sessionId=abc123' }
} ) ; const formData = new FormData ( ) ;
formData. append ( 'file' , fileInput. files[ 0 ] ) ;
axios. post ( '/upload' , formData, { headers: { 'Content-Type' : 'multipart/form-data' }
} ) ; const FormData = require ( 'form-data' ) ;
const fs = require ( 'fs' ) ;
const formData = new FormData ( ) ;
formData. append ( 'file' , fs. createReadStream ( './file.jpg' ) ) ;
axios. post ( 'https://api.example.com/upload' , formData, { headers: formData. getHeaders ( )
} ) ; axios. get ( '/bigfile' , { onDownloadProgress : progressEvent => { console. log ( ` 下載進度: ${ progressEvent. loaded} 字節 ` ) ; }
} ) ; axios. get ( 'https://example.com/bigfile' , { responseType: 'stream' } ) . then ( response => { let total = 0 ; response. data. on ( 'data' , chunk => { total += chunk. length; console. log ( ` 下載進度: ${ total} 字節 ` ) ; } ) ; } ) ;
body-parser 解析請求體(Express 中常用)
作用:在 Express 中,默認 無法讀取 req.body,需要 body-parser 這個中間件來解析 安裝npm i ...
更新:Express 4.16+ 內置了替代方案!從 Express 4.16 起,可以不用單獨裝 body-parser,直接使用即可,把bodyParser改為express
app. use ( express. json ( ) )
app. use ( express. urlencoded ( { extended: true } ) )
app. post ( '/login' , ( req, res ) => { console. log ( req. body)
} ) const express = require ( 'express' )
const bodyParser = require ( 'body-parser' ) const app = express ( )
app. use ( bodyParser. json ( ) )
app. use ( bodyParser. urlencoded ( { extended: true } ) )
app. post ( '/login' , ( req, res ) => { console. log ( req. body) res. send ( '登錄成功' )
} )
commander
npm install commander
作用 命令行工具構建庫 定義命令行命令(如:init、build) 解析參數(如:–force、-o output) 自動生成 --help 幫助文檔 監聽不同命令的回調函數 使用
const { Command } = require ( 'commander' )
const program = new Command ( ) program. name ( 'mycli' ) . description ( '一個自定義 CLI 工具' ) . version ( '1.0.0' )
program. command ( 'init' ) . description ( '初始化項目' ) . option ( '-f, --force' , '是否強制初始化' ) . action ( ( options ) => { require ( './commands/init' ) ( options) console. log ( '初始化項目,參數:' , options) } )
program. parse ( process. argv)
program. parse ( )
program. parse ( [ 'node' , 'cli.js' , 'start' , '--debug' ] ) ;
program. command ( 'build' ) . description ( '構建項目' ) . option ( '-o, --output <path>' , '輸出路徑' , './dist' ) . action ( ( options ) => { require ( './commands/build' ) ( options) console. log ( '構建項目,輸出到:' , options. output) } )
program. command ( 'export' ) . description ( '導出 HTML' ) . option ( '-t, --theme <name>' , '使用的主題' ) . action ( ( options ) => { require ( './commands/export' ) ( options) } ) program. parse ( )
program. action ( ( ) => { console. log ( '你沒有輸入命令,顯示幫助:' ) program. help ( ) } )
program. command ( 'add <name>' ) . description ( '添加一個組件' ) . action ( ( name ) => { console. log ( '添加組件:' , name) } ) . command ( 'export [file]' )
. action ( ( file, options ) => {
} )
"bin" : { "mycli" : "./cli.js"
}
應用:Webpack 和 Vite 本質上就是 CLI(命令行工具),但它們遠不止于此——它們不僅是命令行工具,更是構建工具、模塊打包器、開發服務器、插件平臺、模塊解析與優化 、編譯器集成。
inquirer 詢問 搭配commander
在 commander 中本身不支持交互式選擇,它主要用于解析命令行參數。但你可以結合另一個庫 —— inquirer 來實現命令行中的“詢問”和“選擇”功能,比如讓用戶選擇一個選項、輸入文本、確認操作等。
使用 input 文本輸入 number 數字輸入 confirm 是/否確認 list 單選列表(上下鍵選擇) rawlist 數字選擇(輸入數字) expand 快捷鍵選擇(按字母) checkbox 多選列表(空格選擇) password 密碼輸入(隱藏字符) editor 打開系統編輯器輸入內容 validate: 驗證用戶輸入是否合法 filter: 處理用戶輸入(如轉成大寫) when: 條件提問(根據前面回答決定是否提問) default: 設置默認值 transformer: 美化顯示內容
const { Command } = require ( 'commander' ) ;
const inquirer = require ( 'inquirer' ) ; const program = new Command ( ) ; program. command ( 'init' ) . description ( '初始化項目' ) . action ( async ( ) => { const answers = await inquirer. prompt ( [ { type: 'list' , name: 'framework' , message: '請選擇你要使用的框架:' , choices: [ 'React' , 'Vue' , 'Svelte' ] , } , { type: 'confirm' , name: 'typescript' , message: '是否啟用 TypeScript?' , default : true , } , ] ) ; console. log ( '你的選擇:' , answers) ; } ) ; program. parse ( ) ;
chokidar
作用 高效、跨平臺的 文件監聽庫,基于底層 fs.watch 和 fs.watchFile,但做了大量兼容性增強和性能優化。 使用
事件名 觸發時機 add
文件被新增 addDir
文件夾被新增 change
文件內容發生變化 unlink
文件被刪除 unlinkDir
文件夾被刪除 ready
初始掃描完成 error
出現錯誤
const chokidar = require ( 'chokidar' )
const watcher = chokidar. watch ( 'src/**/*.md' , { ignored: / (^|[\/\\])\.. / , persistent: true
} )
watcher. on ( 'add' , path => console. log ( ` 📄 新增文件: ${ path} ` ) ) . on ( 'change' , path => console. log ( ` ?? 文件變動: ${ path} ` ) ) . on ( 'unlink' , path => console. log ( ` ? 刪除文件: ${ path} ` ) ) const watcher = chokidar. watch ( 'src' , { ignored: / node_modules / , persistent: true , ignoreInitial: false , usePolling: false , interval: 100 , awaitWriteFinish: { stabilityThreshold: 200 , pollInterval: 100 }
} ) watcher. on ( 'change' , ( path ) => { const content = fs. readFileSync ( path, 'utf-8' ) socket. emit ( 'update' , content)
} )
watcher. on ( 'change' , ( filePath ) => { const html = render ( fs. readFileSync ( filePath, 'utf-8' ) ) io. emit ( 'reload' , html)
} )
watcher. on ( 'change' , ( filePath ) => { build ( filePath)
} )
open
版本 open@10+ 默認導出是 ESM(export default),你用 require() 會報錯,如果你用的是 type: “module” 或 .mjs,直接 import chalk from 'chalk'
如果項目是 CommonJS,可以使用 open@9 作用 Node.js 第三方庫,可以幫你 在默認程序中打開 URL、文件、目錄、應用程序等。open 會自動判斷當前平臺(Windows/macOS/Linux)來選擇合適的系統調用,無需你手動處理跨平臺問題。 常用于 CLI 工具中,比如 vite 啟動后自動打開瀏覽器:就是用 open(‘http://localhost:3000’) 實現的。 使用
打開類型 示例 打開網頁 open('https://google.com')
打開本地文件 open('README.md')
打開本地目錄 open('.')
打開應用程序 open('https://google.com', { app: 'firefox' })
選項 作用 app.name
指定打開的應用名(如 chrome
, firefox
, code
, sublime
) app.arguments
傳給應用的參數(如打開無痕、指定窗口等) wait
是否等待程序退出(默認 false
) newInstance
是否強制新開一個程序實例
import open from 'open'
await open ( 'https://example.com' )
await open ( './my-project' )
await open ( './README.md' ) const open = require ( 'open' )
open ( 'https://example.com' )
await open ( '.' , { app: { name: 'code' } } )
await open ( 'https://example.com' , { app: { name: 'google chrome' , arguments: [ '--new-window' ] }
} )
chalk
版本 從 v5 起,chalk 是 ESM-only(只能用 import),如果你項目是 CommonJS,推薦使用 v4 npm install chalk@4
作用:給 命令行文本添加顏色和樣式 使用
import chalk from 'chalk'
console. log ( chalk. green ( '成功!' ) )
console. log ( chalk. red ( '錯誤!' ) )
console. log ( chalk. yellow ( '警告!' ) )
console. log ( chalk. blue ( '信息' ) )
console. log ( chalk. bold. red ( '加粗紅色' ) )
console. log ( chalk. bgYellow. black ( '黑字黃底' ) )
console. log ( chalk. underline. green ( '下劃線綠色' ) )
const error = chalk. bold. red
const warning = chalk. hex ( '#FFA500' )
console. log ( error ( '出錯了!' ) )
console. log ( warning ( '警告:請檢查配置文件' ) ) const log = console. log
log ( chalk. bold. green ( '? 操作成功' ) )
log ( chalk. bold. yellow ( '? 請檢查配置' ) )
log ( chalk. bold. red ( '? 出現錯誤' ) )
log. success ( '成功' )
log. error ( '失敗' )
log. info ( '提示' )
console. log ( ` 這是一個 ${ chalk. green ( '成功' ) } 的例子 ` )
socket.io
作用 Node.js 實時通信庫,基于 WebSocket 封裝,提供客戶端-服務端之間的全雙工通信能力,支持自動重連、斷線重試、事件通信等。 Socket.IO 是建立在 HTTP 或 HTTPS 協議之上的,它需要一個底層的服務器對象來掛載 WebSocket 通道。所以你必須提供一個 HTTP Server 實例。 Socket.IO 必須依附在 HTTP Server 上運行,確實需要一個 HTTP 服務器對象來初始化 Socket.IO,可以手動創建,也可以用 Express 的 app.listen() 返回值來獲取。 應用:實時聊天、實時數據推送(如股票/價格更新)、熱更新、多人協同編輯 對比
功能 WebSocket 原生 Socket.IO 連接握手 手動管理 自動重連 消息結構 字符串 / 二進制 事件機制 廣播支持 ? ? 命名空間 / 房間 ? ? 跨平臺/瀏覽器兼容性 差 好
功能 服務端(socket.io) 客戶端(socket.io-client) 連接 io.on('connection', cb)
io()
發送消息 socket.emit('event', data)
socket.emit('event', data)
接收消息 socket.on('event', cb)
socket.on('event', cb)
廣播消息 io.emit(...)
N/A(由服務端觸發) 私發消息 socket.to(id).emit(...)
N/A 斷開連接 socket.disconnect()
socket.disconnect()
npm install socket. io
npm install socket. io- client
或者HTML 引入 < script src= "/socket.io/socket.io.js" > < / script>
const express = require ( 'express' ) ;
const { Server } = require ( 'socket.io' ) ; const app = express ( ) ;
const server = app. listen ( 3000 ) ;
const io = new Server ( server) ; io. on ( 'connection' , socket => { console. log ( '客戶端連接成功' ) ;
} ) ;
const express = require ( 'express' ) ;
const http = require ( 'http' ) ;
const { Server } = require ( 'socket.io' ) ; const app = express ( ) ;
const server = http. createServer ( app) ;
const io = new Server ( server, { cors: { origin: '*' }
} )
io. on ( 'connection' , ( socket ) => { console. log ( '客戶端已連接:' , socket. id) ; socket. on ( 'chat message' , ( msg ) => { console. log ( '收到消息:' , msg) ; io. emit ( 'chat message' , msg) ; } ) ; socket. on ( 'disconnect' , ( ) => { console. log ( '客戶端斷開:' , socket. id) ; } ) ;
} ) ; server. listen ( 3000 , ( ) => { console. log ( '服務器運行在 http://localhost:3000' ) ;
} ) ;
<! DOCTYPE html >
< html>
< head> < title> Socket.IO 聊天</ title>
</ head>
< body> < input id = " input" placeholder = " 輸入消息" > < button onclick = " send()" > 發送</ button> < ul id = " messages" > </ ul> < script src = " /socket.io/socket.io.js" > </ script> < script> const socket = io ( 'http://localhost:3000' ) ; socket. on ( 'chat message' , ( msg ) => { const li = document. createElement ( 'li' ) ; li. textContent = msg; document. getElementById ( 'messages' ) . appendChild ( li) ; } ) ; function send ( ) { const input = document. getElementById ( 'input' ) ; socket. emit ( 'chat message' , input. value) ; input. value = '' ; } </ script>
</ body>
</ html>
watcher. on ( 'change' , ( filePath ) => { const content = fs. readFileSync ( filePath, 'utf-8' ) io. emit ( 'update' , content)
} )
socket. on ( 'update' , ( html ) => { document. querySelector ( '#app' ) . innerHTML = html
} )
import { io } from 'socket.io-client' const socket = io ( 'http://localhost:3000' ) socket. emit ( 'hello' , 'hi from client' ) socket. on ( 'hello' , ( msg ) => { console. log ( '服務端說:' , msg)
} )
插件機制
作用:支持用戶自定義 核心思路 “插件機制” = 約定接口 + 自動調用 只需要: 允許用戶傳入插件(數組) 在渲染時循環調用這些插件(鉤子) 插件只要滿足某些方法簽名就能工作 定義插件格式
module. exports = { name: 'my-plugin' , beforeRender ( content, options ) { return content} , afterRender ( html, options ) { return html}
}
module. exports = { name: 'copyright-plugin' , afterRender ( html ) { return html + ` <footer><p style="text-align:center;color:#aaa;">? hello 2025 </p></footer> ` }
}
修改執行文件來支持插件機制,引入插件加載器.js,用它加載所有插件,循環分別在渲染前/渲染后執行對應函數即beforeRender/afterRender
const fs = require ( 'fs' )
const path = require ( 'path' )
const loadPlugins = require ( './plugins' ) function operator ( filePath, theme = 'default' , pluginPaths = [ ] ) { let Content = fs. readFileSync ( filePath, 'utf-8' ) const plugins = loadPlugins ( pluginPaths) for ( const plugin of plugins) { if ( plugin. beforeRender) { Content = plugin. beforeRender ( Content, { theme } ) } } const renderer = new xx ( ) let NewContent = renderer. render ( Content) for ( const plugin of plugins) { if ( plugin. afterRender) { NewContent = plugin. afterRender ( NewContent, { theme } ) } } const cssPath = ` /styles/ ${ theme} .css ` return `
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8" /><title>Preview</title><link rel="stylesheet" href=" ${ cssPath} ">
</head>
<body><div class="content"> ${ NewContent} </div>
</body>
</html> `
} module. exports = operator
const path = require ( 'path' )
const fs = require ( 'fs' ) function loadPlugins ( pluginPaths ) { const plugins = [ ] for ( const pluginPath of pluginPaths) { const abs = path. resolve ( process. cwd ( ) , pluginPath) if ( fs. existsSync ( abs) ) { const plugin = require ( abs) plugins. push ( plugin) } else { console. warn ( ` ?? 插件 ${ pluginPath} 不存在 ` ) } } return plugins
} module. exports = loadPlugins
. option ( '--plugin <path...>' , '加載插件' )
插件注冊 API 提供 use() 方法,內部注冊插件 方法封裝成類
const fs = require ( 'fs' )
const path = require ( 'path' ) class PluginManager { constructor ( ) { this . plugins = [ ] } use ( plugin ) { if ( typeof plugin === 'object' ) { this . plugins. push ( plugin) } else { console. warn ( ` 插件無效: ${ plugin} ` ) } return this } render ( filePath, theme = 'default' ) { const cssPath = ` /styles/ ${ theme} .css ` return `
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8" /><title>Preview</title><link rel="stylesheet" href=" ${ cssPath} ">
</head>
<body><div class="content"> ${ htmlContent} </div>
</body>
</html> ` }
} module. exports = new PluginManager ( )
const pluginManager = require ( './pluginManager' )
pluginManager. use ( require ( './plugins/copyright-plugin' ) ) . use ( require ( './plugins/toc-plugin' ) )
const html = pluginManager. render ( filePath, theme)
const pluginManager = require ( '../lib/pluginManager' )
if ( options. plugin && options. plugin. length) { for ( const pluginPath of options. plugin) { const abs = path. resolve ( process. cwd ( ) , pluginPath) const plugin = require ( abs) pluginManager. use ( plugin) }
}
if ( options. export) { const html = pluginManager. render ( filePath, options. theme) fs. writeFileSync ( options. export, html)
} else {
}
插件系統生命周期鉤子 init、beforeRender、afterRender、onError Node.js 本身并沒有統一的“插件生命周期鉤子”機制,不像 Webpack、Fastify、Nuxt 等框架那樣提供標準化的鉤子系統。但在 Node.js 的生態中,很多框架和工具都實現了自己的插件機制和生命周期鉤子。 把這些插件自帶的鉤子都補進 pluginManager.render() 中,非常好擴展。
生命周期鉤子名 觸發時機 是否異步支持 init(options)
插件初始化時 支持 beforeRender(content, options)
渲染前 支持 afterRender(html, options)
渲染后 支持 onError(err)
渲染出錯時 支持 onFinish()
渲染流程結束 支持
use ( plugin ) { if ( plugin && typeof plugin === 'object' ) { if ( typeof plugin. init === 'function' ) { try { plugin. init ( ) } catch ( e) { console. warn ( ` 插件 ${ plugin. name} 初始化失敗: ` , e) } } this . plugins. push ( plugin) } return this } catch ( err) { console. error ( ` ? 渲染失敗: ${ err. message} ` ) for ( const plugin of this . plugins) { if ( typeof plugin. onError === 'function' ) { try { plugin. onError ( err) } catch ( e) { console. warn ( ` 插件 ${ plugin. name} onError 失敗: ` , e) } } } } for ( const plugin of this . plugins) { if ( typeof plugin. onFinish === 'function' ) { try { plugin. onFinish ( ) } catch ( e) { console. warn ( ` 插件 ${ plugin. name} onFinish 失敗: ` , e) } } }
module. exports = new PluginManager ( )
module. exports = { name: 'dev-logger' , init ( ) { console. log ( '[dev-logger] 插件已加載' ) } , beforeRender ( content ) { console. log ( '[dev-logger] 渲染前內容長度:' , content. length) return content} , afterRender ( html ) { console. log ( '[dev-logger] 渲染后 HTML 長度:' , html. length) return html} , onError ( err ) { console. error ( '[dev-logger] 渲染異常:' , err) } , onFinish ( ) { console. log ( '[dev-logger] 渲染流程完成 🎉' ) }
}
插件默認目錄 自動加載 ./plugins/*.js 封裝自動加載邏輯
const path = require ( 'path' )
const fs = require ( 'fs' ) function loadLocalPlugins ( ) { const pluginsDir = path. resolve ( process. cwd ( ) , 'plugins' ) const pluginList = [ ] if ( ! fs. existsSync ( pluginsDir) ) return [ ] const files = fs. readdirSync ( pluginsDir) for ( const file of files) { const ext = path. extname ( file) if ( ext === '.js' ) { const fullPath = path. join ( pluginsDir, file) try { const plugin = require ( fullPath) pluginList. push ( plugin) } catch ( e) { console. warn ( ` ?? 插件加載失敗: ${ file} ` , e) } } } return pluginList
} module. exports = loadLocalPlugins
module. exports = { name: 'copyright' , order: 10 , afterRender ( html ) { return html + '<footer>? 2025</footer>' }
}
pluginList. sort ( ( a, b ) => ( a. order || 0 ) - ( b. order || 0 ) )
搭配上之前的手動傳參,實現自動手動一起加載
插件配置文件 支持讀取 .previewrc 配置文件加載插件 讓用戶通過項目根目錄的 .previewrc 文件自動配置插件和主題,避免復雜命令行參數。
{ "theme" : "hacker" , "plugins" : [ "./plugins/copyright-plugin.js" , "./plugins/toc-plugin.js" ]
}
const fs = require ( 'fs' )
const path = require ( 'path' ) function loadConfig ( ) { const configPath = path. resolve ( process. cwd ( ) , '.previewrc' ) if ( ! fs. existsSync ( configPath) ) return { } try { const raw = fs. readFileSync ( configPath, 'utf-8' ) const config = JSON . parse ( raw) return config} catch ( e) { console. warn ( '?? 配置文件 .previewrc 加載失敗:' , e. message) return { } }
} module. exports = loadConfig
const rc = loadConfig ( )
const theme = options. theme || rc. theme || 'default'
const allPlugins = [ ... ( rc. plugins || [ ] ) , ... ( options. plugin || [ ] )
]