把部落格後台搬進 VS Code:修圖、上傳、管理圖片、AI 產生分類與標籤一次搞定
在之前的 自架 WordPress 用了 7 年,最後又回到自己寫程式,雖然回到用 VS Code 編輯文章的模式,但其實還是有個網頁程式介面,用來上傳圖片,還有設定一些文章資訊,例如挑分類、挑相關文章等等,也可以線上編輯文章。
但其實很多時候還是有不少冗餘的操作步驟:
- 在 Photoshop 裡面改圖,然後另存新檔,選擇 webp,選擇存檔位置,然後又要到別的視窗、登入、選擇存檔位置,上傳圖片。
- 管理線上圖片、管理本地圖片,把圖片插入文章,要在多個視窗來回切換,非常浪費生命。
- 有些圖片編輯需求其實非常普通,連開小畫家都太多餘,我還要找小畫家的圖示藏到開始功能表哪邊去了?
- 為了幫圖片取個語意化檔名,還要小心不要撞名,又要花一些時間跟步驟。
既然是在 VS Code 編輯文章,所以做了個 VS Code 擴充套件,在多個視窗來回切換很麻煩? 那就直接修圖、上傳、管理圖片都在同一個視窗做完。大幅提升文章撰寫、圖片管理以及網站管理的效率。
本文介紹一下擴充套件有什麼功能,還有 VSC 擴充套件跟一般的網頁程式又有什麼特殊之處。
擴充套件功能介紹
因為本來已經有一套寫好的網頁程式,所以不用在擴充套件內保存什麼雲端的金鑰、實作整套流程邏輯,有些圖片上傳邏輯就是直接呼叫那邊的東西,在 VS Code 擴充套件內把資料顯示成方便操作的 UI 介面。
影片中還是非常早期的版本,但懶得再重錄一遍,下面大致介紹一下做了哪些功能。
1. 上傳
支援圖片的全自動處理與上傳,能快速將本機圖片(支援 JPG, PNG, GIF, WebP)拖曳至標示區,並提供壓縮選項(設定最大寬度與啟用 WebP 轉換)。自動轉檔壓縮上傳至雲端。
上傳完成後可以直接複製 HTML 語法,或一鍵將圖片標籤插入目前的 Markdown 文件指標處,完全不需離開編輯器。
2. 瀏覽
提供雲端物件儲存服務的檔案瀏覽介面,可以直接在編輯器內查看已經上傳的圖片清單與縮圖。
目前有做的功能:
- 檔名搜尋
- 快速複製圖片網址
- 一鍵插入文章
- 刪除圖片
- 進入圖片編輯模式
省去在瀏覽器切換雲端儲存空間畫面的繁瑣步驟。
3. 裝置同步檔案
有時候圖片要從手機傳到電腦(我主力是 iPhone 配 Windows),以前是用 Local Send 的 app,每次要用時還要另外打開,然後有時候傳完沒按完成,下次要傳還會卡住,有時候則是陷入某種不明的連線錯誤狀態。
如果電腦跟手機都在同一個內網,那就直接讓擴充套件在電腦上啟動一個小型的 Local Server,並產生專屬連線的 QR Code。

手機能快速掃描,開啟一個檔案上傳的網頁,並直接將手機上的照片影片丟到電腦中指定的資料夾。
4. 文章
專為 Markdown 文章撰寫設計的輔助區域。包含:
- Front Matter 管理:快速自動取得/插入文章 ID、編輯標題,還能結合 AI 模型(如 Gemini 等)自動分析內容並產生摘要(Description)。
- 分類與標籤選擇:提供一個介面手動選擇分類、標籤、相關文章,也能利用 AI 自動智慧推薦適合的關聯標籤與相關文章,最後一鍵組合出一整段完美格式化的 Front Matter 自動插入文章的最上方。
- 還做了個 MCP,裡面有幾個 tool,會自動讀取文章資訊和分類清單,把生成標題、描述...之類的全部用 AI 跑完。

理論上把文章本文打完,就能直接讓 AI 把剩下的事做完,但後來還是很少用這個全自動功能。
因為用便宜或免費 AI 模型,生出來的文章標題、描述之類的效果令人很不滿意,一點靈魂都沒有。大概只適合拿來生成歲月靜好、和和氣氣、毫無個性的商業文章。
不過 AI 是不會負責背鍋的,AI 效果不好,只能怪我這個人類文章不夠直白、文章太長耗 token,或是沒有試到最適合每種模型的 prompt。
5. 指令
部落格專案的 package.json 內設定的指令多到記不起來,所以把指令&選項參數直接整合成一堆 UI,例如:
- 建置或壓縮 CSS/SCSS/JS
- 呼叫某些線上 API
- Markdown 轉 HTML (這個後來直接在 git hook pre-commit 做掉,文章草稿目錄有檔案變動,就自動生成)
- 啟動本地端測試機的 Docker
- 備份 Threads 文章
還有其他指令就不列了。
6. 圖片編輯
在側邊欄選擇本地圖片或雲端圖片後,可直接進入圖片編輯器,編輯完直接上傳。
功能包含:
- 圖片裁切
- 馬賽克筆刷
- 插入方塊(填滿方塊可用於遮擋資訊,鏤空方塊可用於標記區域)
- 加箭頭
- 圖片旋轉/水平翻轉
- 本地儲存(覆蓋原檔、另存新檔)
- 雲端儲存(覆蓋原檔、另存新檔)
- AI 生成檔名
這樣既保留了自架部落格的優點(所有檔案在本機都有一份,不怕平台跑路),又能把所有文章編輯流程中會用到的操作,通通整合到一起。
以下紀錄分享一些 VS Code 套件的開發筆記:
開發 VS Code 擴充套件與一般寫網頁程式有什麼不同?
只是要做一個有自己名字的 VS Code 擴充套件,可以使用 VS Code Extension Generator,安裝&執行 npx --package yo --package generator-code -- yo code之後,會引導完成一個基本的 VS Code 擴充套件專案目錄架構。
真的要做些什麼功能,可以參考微軟官方提供的 vscode-extension-samples。裡面有幾十種範例,每個範例還會列出用到的 API,非常適合新手入門。
當然也可以隨便找個 Copilot, Antigravity, Codex 之類的本機 coding agent,直接向 AI 提出想法開始開發,展開疊床架屋加功能的旅程。
探索後發現,要開發一個 VS Code 擴充套件,可以用熟悉的 Web 技術(HTML / CSS / JavaScript),但整體架構和一般網頁程式有幾個極大的區別。
1. 樣式無縫融入 VS Code 的主題變數
在寫網頁前端時,要把預設的灰色按鈕、輸入框等表單物件,從醜醜的預設樣式調整成客製化樣式,就是一個工程問題。少則寫幾十行起跳的CSS,大到引入一些 UI framework,然後被開發者在聖誕節送彩蛋惡搞。
一開始發現程式碼裡並沒有撰寫太多複雜的色彩CSS、跨平台的判斷規則,但做出來的 UI(按鈕顏色、輸入框、文字)不管換到什麼地方,都跟 VS Code 幾乎融為一體?

分別是 Windows 上的 Cursor/VSC,還有 Mac OS 上的 VSC
這是因為 VS Code 有一堆內建的 CSS 變數(CSS Variables)可以讓我們的 Webview 網頁使用,例如:
var(--vscode-font-family):跟隨編輯器設定的標準字體。var(--vscode-button-background):取得編輯器當前主題的按鈕主色系。var(--vscode-editor-foreground):目前的文字顏色。
當我切換 VS Code 編輯器的深色/淺色主題時,只要套件的 CSS 使用這些變數,顏色也會自動切換。不論是哪一種佈景主題都會像是原生內建的功能,減少突兀感。
相關補充資源:
- VS Code CSS Theme Completions 一個 VS Code 套件,可以在寫 CSS 時自動提示還有哪些內建變數能用。
- Theme Color | Visual Studio Code Extension API 一個大表,解釋每一種顏色名稱會顯示在哪裡。
- List of CSS variables accessible in an extension WebView #2060 關於變數清單大表的討論
2. 沙盒限制與 Message Passing 架構
網頁有前後端(跑在client side browser的程式,跟跑在伺服器上的程式),到了 VS Code 擴充套件,勉強也能分成跑在 Webview 的前端和 Extension Host 後端。
讀寫本機檔案與系統環境:前端 Webview 的 JS 本身不能直接用 fs.readFile 去讀取電腦的檔案,也不能直接存取 process.env 讀取系統環境變數。
如果需要顯示這些系統變數或硬體狀態,必須靠後端的 Extension Host 讀取完畢後,再把資料透過 postMessage 傳遞給前端 Webview 顯示。
範例:將系統變數傳給前端顯示
// 1. 在後端 Extension Host (Node.js 環境) 中讀取並傳送:
const systemEnv = process.env;
webviewPanel.webview.postMessage({
command: 'showEnvData',
data: systemEnv
});
// 2. 在前端 Webview (前端瀏覽器環境) 中接收並印出:
window.addEventListener('message', event => {
const message = event.data;
if (message.command === 'showEnvData') {
console.log('從後端收到的環境變數:', message.data);
document.getElementById('env-display').innerText = JSON.stringify(message.data, null, 2);
}
});
呼叫系統指令:前端 Webview 本身沒有權限直接使用 child_process 執行 Shell 或 Terminal 指令。如果擴充套件需要呼叫外部 CLI 工具(例如有實作一個用本機的 Gemini CLI 幫圖片 AI 命名功能),必須透過 Message Passing 把指令傳給後端的 Extension Host,幫人類代勞。
範例:請後端代為執行 Gemini CLI 命名圖片
// 1. 在前端 Webview (前端瀏覽器環境) 中發送請求:
vscode.postMessage({
command: 'runAiRename',
filePath: 'C:/temp/image.png'
});
// 2. 在後端 Extension Host (Node.js 環境) 接收後執行 CLI:
import * as child_process from 'child_process';
webviewPanel.webview.onDidReceiveMessage(message => {
if (message.command === 'runAiRename') {
// 在後端擁有完整權限,調用 child_process 執行終端機指令
const cmd = `gemini-cli "請幫我分析這張圖片並產生一個英文檔名" "${message.filePath}"`;
child_process.exec(cmd, (error, stdout, stderr) => {
if (error) {
console.error(`執行失敗: ${error}`);
return;
}
// 執行完畢後,再把結果傳回前端顯示
webviewPanel.webview.postMessage({
command: 'aiRenameResult',
result: stdout.trim()
});
});
}
});
不同作業系統差異:有些在傳統網頁瀏覽器能做的事情,在 VS Code 版本的 Webview 中可能水土不服。例如存取剪貼簿,在 Windows 上 Webview 的 navigator.clipboard.readText() 可能會因為缺少使用者明確手動授權而直接報錯,但在 macOS 上或許能順利執行(或反之)。因此,凡是涉及系統資源存取的動作,最好的做法都是交由 VS Code 的擴充主程式 (Extension Host) 機制來處理。
擴充套件裡面用到的相依套件(好繞口),也可能有跨平台問題,如本文介紹的套件有一些圖片處理功能,有用到 sharp,在 Windows 上跑得好好的,在 macOS 安裝後完全打不開,又需要用 sharp-libvips 處理。
另外還有關於 Message Passing 傳遞的限制與 Timeout:
- 非同步特性:
postMessage是一種 fire-and-forget(射後不理)的操作,它本身沒有像 Ajax/Fetch API 那樣自帶的 Callback 或超時中斷 Timeout 機制。 - 自訂 Timeout 實作:VS Code 底層並沒有為
postMessage設定硬性的連線超時限制。
如果需要發送 API 請求到外部網站、執行長時間腳本,前端 Webview 必須自己設定 setTimeout 來管理等待頁面,如果 10 秒後沒收到擴充主程式回傳的對應訊息,前端就自行解除 loading 狀態並報錯;Extension Host 也應該在 Node.js 中處理指令執行的超時異常。
3. 可延伸執行任何程式語言,不僅限於 Node.js
延續上一點,雖然前端 Webview 受到沙盒限制,但負責當大腦的滿血 Node.js 擴充主程式 (extension.ts / Extension Host) 卻可以無限制地存取電腦。
我們可以將 VS Code 的前端 UI 當成一個 GUI 遙控器,點擊按鈕後,由擴充主程式去呼叫電腦裡任何已安裝的環境或語言。
不只是本機腳本,結合 Node.js 與底層指令,擴充主程式可以直接利用 https:// 協定發送請求去外部網站抓資料、利用 ssh:// 機制操作遠端伺服器,甚至執行 git push、curl、lftp FTP 傳檔等跨網路任務,還有執行 CLI 終端機指令、或是呼叫本地端 docker 服務,將 VS Code 輕量化的 IDE 轉變成能對外聯絡的樞紐中心。
4. 狀態保存與持久化登入機制 (State & Auth)
一般全端 Web 網站架構,前端常會依賴 Browser Cookie,後端則依賴 PHP Session 或 Token 來維持使用者的登入狀態。但在 VS Code 擴充套件中,這些傳統網頁機制並不適用。
例如要做一個「需要帳號密碼登入才能使用的擴充套件」?
Webview 就像無痕瀏覽器(Cookie 無法跨工作區/重開機存活),Webview 中雖然可以使用 document.cookie 或 localStorage,但它們的生命週期極其不穩定。每次關閉 Webview 面板(例如切換到別的檔案再切回來),它內部的 DOM 和 JavaScript 變數預設都會重新初始化。即使資料當下存進去,重開 VS Code 或是換一個專案資料夾開啟套件,Cookie 跟 Storage 往往就會直接被清空,完全無法達到「持久化登入」的目的。
持久化的解決方案:Extension Context:擴充套件主程式(Extension Host)提供了專屬的安全儲存 API。我們必須利用擴充主程式把登入狀態(Token、API 金鑰或甚至 Cookie 字串)存放在這裡,即使關閉 VS Code 電腦重開機也不會消失。
context.globalState:類似跨專案的 LocalStorage,適合存一些全域的雜湊設定(例如:是否開啟某個提醒)。context.workspaceState:針對目前打開的這個專案資料夾存放設定,換個專案資料夾就不共享。context.secrets(SecretStorage):這是非常重要的一點!如果要存放使用者的登入 Token、金鑰或是密碼,一定要將其保存在context.secrets。它是與作業系統層次的安全儲存區域(Windows 的認證管理員 Credential Manager、macOS 的鑰匙圈 Keychain)綁定的,能夠最高等級加密保護敏感憑證。
因此,實作登入流程通常是:Webview 向使用者顯示登入表單 -> 透過 postMessage 傳遞帳密給 Extension Host -> Extension Host 向遠端伺服器驗證取得 Token (或 Session ID) -> 存入 context.secrets 中**。
下次打開套件時,主程式直接從 secrets 撈出憑證,並在 Node.js 中使用它來發起 API 呼叫(例如在 request header 放入 Authorization,或是自己組裝 Cookie header 送出),Webview 開啟時只要向擴充主程式請求登入狀態就好,就不需再登入一次了。
使用外部網頁登入
但大家可能也看過例如 Cline 跟一些擴充套件,點擊登入後,不會在 VS Code 裡面輸入帳密,而是打開電腦上的預設瀏覽器,在網頁上授權完畢後,VS Code 會突然彈出一個提示框問「是否允許擴充套件開啟此 URI?」,然後套件就神奇地變成登入成功的狀態了。
這是擴充套件最正統的 OAuth 登入做法,它不僅比在 Webview 裡刻表單更安全,還能直接沿用使用者在 Chrome 上已經登入的社群帳號(不用重新輸入密碼),實作概念大概是這樣:
- 呼叫系統瀏覽器:在套件主程式中,遇到需要登入的指令時,使用
vscode.env.openExternal(vscode.Uri.parse('https://your-auth-site.com/login'))喚醒使用者的預設瀏覽器(Chrome/Edge)。 - 網頁端驗證與深層跳轉 (Deep Link):使用者在網頁上完成 Google/GitHub 登入後,網頁伺服器不能像往常一樣跳轉回
https://...,而是要設定重新導向(Redirect)到 VS Code 的專屬深層連結,格式通常為:vscode://我的套件發行商ID.我的套件ID/callback?token=abc...。 - VS Code 接收回應:在套件主程式的
activate中,需要提早註冊一個 URI 處理器 (vscode.window.registerUriHandler)。當使用者在網頁同意跳轉回 VS Code 且按下允許時,這個 Handler 就會接管並解析網址列中的?token=abc,接著一樣把取得的 Token 存進context.secrets中完成登入手續!
5. 套件產生的實體檔案該存在哪裡?(File Storage)
如果套件執行後會產生一份實體檔案(譬如 SQLite 資料庫檔、設定的 JSON 檔、快取圖片檔案),肯定不希望它下次開機就不見。在 VS Code 擴充套件中,檔案絕對不能隨便存在擴充套件自己的安裝資料夾(__dirname)底下,因為每次套件更新,那個資料夾就會整個被清空重置。
如果下次執行時需要根據那個檔案的資訊來執行,VS Code 提供了兩個專屬、安全且保證不會被隨意刪除的儲存資料夾路徑,讓的 Node.js 主程式去進行 fs.writeFile 寫入:
context.globalStorageUri (全域儲存空間)
用途:如果這個檔案跟任何特定的專案無關,只要是同一個使用者開的 VS Code 都能共用這份資料(例如:使用者的個人 Profile 頭像快取、全域的 SQLite 字典庫、跨專案生效的基礎設定檔 config.json)。
位置:通常位於使用者的個人資料夾下(類似 ~/.config/Code/User/globalStorage/套件ID/)。
context.storageUri (工作區專屬儲存空間)
用途:如果這個檔案只針對目前打開的這個專案(Workspace)有效。離開這個資料夾打開別的專案,讀到的就會是另一份獨立的檔案(例如:針對這個專案掃描後產生的快取分析包 build-cache.json,或是針對這個專案綁定的特定遠端伺服器 .env.local 備份檔)。
位置:通常位於工作區配置下(類似 ~/.config/Code/User/workspaceStorage/隨機工作區Hash/套件ID/)。
實作方式就是在 extension.ts 的 activate 函數中,先確定這個目錄存在(可以用 fs.mkdirSync(context.globalStorageUri.fsPath, { recursive: true }) 建立),然後後續的所有檔案讀寫操作(fs.writeFileSync / fs.readFileSync),都指定存放在這個 fsPath 資料夾底下。
這樣不論套件怎麼更新、電腦怎麼重啟,只要使用者沒把套件徹底反安裝清資料,這個檔案就會一直存在!
6. Webview 裡可以存 IndexedDB 嗎?有「同網域」概念嗎?
在一般瀏覽器中,IndexedDB 或 LocalStorage 的資料存取權限是綁定「網域起源 (Origin)」的(譬如 https://example.com)。
但在 VS Code 的 Webview 中:
- 網域來源 (Origin) 極度不穩定:Webview 運作的特殊協定通常會帶有一個長長的隨機雜湊碼(像是
vscode-webview://01234567-89ab...)。 - 強烈不建議使用 IndexedDB 等前端儲存庫:因為這個來源 ID 在更新 VS Code 版本、重開機、或是重新開啟 Webview 面板時,可能隨時會變動,導致「昨天存在這個網域 IndexedDB 的資料,今天因為被判定為不同網域而再也抓不到」。
替代的現代作法:如果需要存取結構化的大量資料(類似於 IndexedDB 的專案),正確的思路一定是:在 Extension Host 端引入真正的本地資料庫套件(例如 sqlite3、lowdb 等),並將產生的 .sqlite 或 .json 資料庫實體檔案,妥善存放在上一點提到的 context.globalStorageUri 路徑下。
Webview 前端 UI 只是單純的畫面呈現,當需要讀取或寫入一筆資料時,它就負責用 postMessage 向 Node.js 下查詢指令,再接收回傳的結果。
7. 如何做到產生檔案後,讓使用者下載
一般網頁程式如果產生了報表檔案給人下載,通常會在前端產生一個 <a href="blob:..." download="file.csv">下載</a> 的連結,然後依賴瀏覽器處理跳出儲存與否的行為。
然而在 VS Code 擴充套件裡,前端 Webview 是無法直接觸發瀏覽器的預設下載攔截系統的。
VS Code 有它更「原生系統化」的實作 SOP:
- 前端提出下載請求:Webview 把產生好的檔案資料字串(譬如一個長長的 CSV 或 Base64 圖片)打包放入
postMessage({ action: 'saveFile', data: '...' })傳給後端的 Extension Host 主程式。 - 呼叫作業系統原生對話框 API:Node.js 擴充主程式收到要求後,呼叫 VS Code 強大的專屬 API
vscode.window.showSaveDialog()。這會直接彈出一個原生的「另存新檔」作業系統視窗,還能自訂預設檔名跟副檔名的過濾器,讓使用者去選在他們這台電腦上想存放在哪裡。 - 由 Node.js 寫入檔案並通知:使用者選好儲存路徑(譬如
D:\Downloads\my-report.csv)按下確定後,Node.js 就會拿到這組完整的自訂絕對路徑。接著就是發揮 Node 本色,主程式呼叫強大的fs.writeFileSync(path, data)直接默默並有效率地把資料寫進使用者的硬碟裡,然後跳出一個 VS Code 的通知vscode.window.showInformationMessage('匯出成功!已儲存在:' + path)。
這就是 VS Code 套件比瀏覽器單純前端更強大且能深度整合系統的地方,它能直接操作任何想放的檔案位置,而不被強迫受限在瀏覽器的 Downloads 資料夾與行為中!
擴充套件 UI 設計與佈局的常見名詞與規範
如果不熟悉 VS Code 擴充套件的專門術語,想要新增功能時可能會不知道怎麼下達指示。以下是常見的 UI 區塊,以及其設計限制與相關規範,幫助我們未來溝通更精準:
1. Activity Bar (活動列)
這是 VS Code 最側邊(預設在左側最旁邊)那條直直的圖示列(包含我們常見的檔案總管圖示、搜尋圖示、原始碼控制的那排 icon)。我們的「文章編輯工具」圖標就是實作在這裡。
關於 Activity Bar 圖示的設計鐵律與實作:
- 必須是 SVG 格式:不支援 PNG、JPG 等點陣圖,以確保在高解析度螢幕上縮放不失真。
- 單色線條風格 (Monochrome):圖示不可以自帶顏色(不能寫死
fill="red")。通常整個 SVG 必須是形狀乾淨的單色線條。 - 自動適應主題色的魔術:只要 SVG 是單色(通常預設黑色或白),VS Code 的渲染引擎會自動介入幫它上色。在深色主題會自動反白,在淺色主題會變深灰;圖示被「選取(Active)」時,會自動染上主題的高亮色(Focus Color)。這就是為何圖示都能完美融入主題的原因。
如果不會畫 SVG 或是想極度貼近原生,VS Code 有內建一套名為 Codicons 的百種圖示庫。開發時只要打一個大號 $(gear) 編輯器就會顯示成齒輪。可以查閱代號大表: Product Icon Reference。
除了這些一定要放一張圖片當圖示的,如果按鈕也要有圖示,最省事的就是直接用 emoji...
2. Primary Sidebar (主側邊欄)
點擊 Activity Bar 上面的圖示後,展開的那一條寬面板塊(例如 Explorer 的樹狀目錄)。我們目前將功能註冊為 Webview View 並掛載在這裡面,這樣就能一邊開著文章進行編輯,一邊從左側存取輔助功能。
3. Editor Title (編輯器標題列 / 索引籤列)
像 Claude 的 VS Code 套件,會在平常上面顯示檔案的頁籤(Tab)最右邊、也就是分割視窗按鈕的旁邊,放上一個專屬的小 Icon(點下去可能抓取代碼給 AI 看或打開特殊面板)。
這個兵家必爭的黃金地段,在 VS Code 的定義中叫做 editor/title 選單。
- 如何出現在這裡:開發者需要在
package.json的menus節點下,針對editor/title註冊一個指令(Command),並綁定一個圖示。 - 條件顯示 (When Clause):為了避免這個區域被數十個套件塞爆,VS Code 鼓勵且允許透過
when條件來控制圖示什麼時候該出現。例如可以設定when: "resourceLangId == markdown",讓小按鈕只在正在編輯 Markdown 檔案時才優雅地浮現。
4. Secondary Sidebar (次側邊欄)
VS Code 支援在畫面的另一側開啟另一個輔助的側邊欄。使用者可以手動打開它,這個位置非常適合放一些不會頻繁操作,但時常需要瞄一眼的工具箱。
沒有絕對的左邊或右邊:在 VS Code 裡使用者可以隨意右鍵 Activity Bar 選擇「將活動列移至右側」,這樣 Primary Sidebar 就會跑去右邊,而 Secondary Sidebar 就會被擠到對面去。因此寫 Webview UI 時,排版必須是彈性 Responsive 的,不能預設「我的面板一定在左側」。
載入時機與效能 (Loading 很久的原因):與主側邊欄不同,Secondary Sidebar 經常是被延遲載入的。如果套件把重量級的 Webview (iframe) 塞在這裡,VS Code 要在底層去協調多個套件搶這塊側邊欄資源,渲染順序較低時,就會發生卡頓或白畫面 Loading 特別久。建議在這裡盡量使用原生的 TreeView(輕量),而少放沉重的自訂 HTML。
5. Panel (底部面板)
這通常位於程式碼編輯區的「正下方」,裡面包含了最常使用的「終端機 (Terminal)」、「輸出 (Output)」、「除錯主控台 (Debug Console)」。如果功能會產生大量的文字輸出(例如跑 npm build),通常就會把它印在這個區域。
6. Status Bar (狀態列)
位在 VS Code 最底部那條極度細長的橫條。適合用來放文字提示、狀態燈號(Status Bar Item)。
例如,未來如果想顯示「API 連線正常 🟢」,或是放一個「立刻上傳圖片」的小文字按鈕,就可以將功能放在 Status Bar。
狀態列的左右與優先級 (Priority):
建立 Status Bar Item 時,開發者必須指定它要放在左邊還是右邊,以及它的排序優先級:
- 左側 (
vscode.StatusBarAlignment.Left):通常用來放跟整個工作區 / 專案狀態相關的資訊。例如 Git 分支狀態、同步狀態、專案是否正在建置中。 - 右側 (
vscode.StatusBarAlignment.Right):通常用來放跟編輯器本身 / 當前開啟檔案相關的設定。例如目前的檔案編碼 (UTF-8)、縮排幾格、TypeScript 版本。 - Priority 權重分數:可以給定一個數字(例如 100)。數字越高的項目會越靠近邊緣(越靠左或越靠右),讓重要的資訊不會被擠到中間被隱藏起來。
7. Webview Panel (網頁視圖面板)
如果覺得側邊欄(Sidebar)太窄了,不想要把介面擠在小小的地方,那麼可以選擇使用 Webview Panel。這會直接佔據一個完整的「程式碼編輯器標籤頁(Editor Tab)」空間。
就像一些 Markdown 套件的預覽畫面那樣,適合用來顯示大型圖表或複雜的管理後台。
我用這功能做了一個圖片編輯介面。
8. Command Palette (命令選擇區)
按下 Ctrl+Shift+P (或 Cmd+Shift+P) 彈出的指令輸入視窗。這是 VS Code 中所有功能與指令的入口,我們可以讓任何功能都不依靠滑鼠點擊按鈕,而是將指令(Command)註冊在這裡,純鍵盤操作就能發動。
能夠做出「自由拖曳、懸浮在桌布上」的小視窗嗎?
很多開發者想做個像「桌面便利貼」一樣,可以隨意自由拖曳、不黏死在 VS Code 主視窗上的獨立小面板,VS Code 的 Extension API 沒有提供任何指令讓人「憑空創造一個獨立的 OS 層級懸浮視窗」。透過擴充套件畫的所有 UI,最初一定都得掛載在上述的 Sidebar、Panel 或是 Editor Tab 裡面,乖乖聽從 VS Code 的規則。
到了 VS Code 版本 1.85(大約是 2023 年底,撰文時版本號已經來到了 1.1百多),增加了「拖曳獨立視窗 (Floating Windows)」的新特性。使用者只要用滑鼠按住那個標籤頁,往 VS Code 視窗外面的系統桌面上「一拉」(或是在 tab 上右鍵選擇在新視窗開啟),它就會變成一個獨立浮動的作業系統視窗了!
但這必須把功能實作在 Webview Panel(剛剛第6點介紹的,佔據一個標籤頁的模式),才能達到這種效果,做在側邊欄什麼的話都沒用。
在 VS Code 中,如果使用者將 Webview 分頁「拖拉」到另一個分割區或完全獨立的新視窗時會發生一個怪問題:原本在 Webview 裡操作的狀態(例如使用者剛剛塗鴉的畫面、輸入一半的表單)可能會瞬間不見!
這是因為視窗或分割區改變時,VS Code 底層為了重新渲染,會把 Webview 的 iframe 徹底銷毀並在新的位置重建。就算在建立 Webview 時加上了 { retainContextWhenHidden: true },這個屬性也只能保證「切換到別的分頁再切回來」時不被銷毀,無法保護「跨越視窗」的重建行為。
開發者必須透過監聽 panel.onDidChangeViewState 事件,當發現面版重新變為可見狀態時,主動把暫存在 Extension Host 記憶體裡的資料重新塞回 Webview 裡。
因為我這個工具的資料只有「圖片」(可以暫存並轉成 Base64 字串),要把圖片資料重送回去還算簡單。如果開發的是一個極其複雜的互動式儀表板(例如有幾十個 Filter、分頁、展開的樹狀圖)或表單,要在重建時把所有狀態完美還原,可能會寫到讓人懷疑人生。
刻擴充套件的 UI,用原生的 TreeView 跟用 Webview 的差異
本套件是使用 Webview 來刻擴充套件的 UI,有些時候覺得效能不夠好。
其實還有另一種刻擴充套件 UI 的方式,就是用原生 TreeView,開發邏輯與外觀呈現有著天壤之別:
HTML Webview (自由奔放但肥大)
本質:就是一個跑在 iframe 裡的微型瀏覽器。
外觀:可以隨心所欲寫 HTML, CSS 甚至套用 Vue/React,做出任何想得到的酷炫按鈕跟動畫。
缺點:吃資源、記憶體佔用高、載入慢。而且需要自己手動發送 postMessage 才能跟 VS Code 擴充主程式 (Extension Host) 溝通。
原生 TreeView (極度輕量但死板):
本質:VS Code 原生的 UI 元件。就像 VS Code 左側常見的「檔案總管」或「原始碼控制」那樣的樹狀列表。
外觀表現:完全不能寫 HTML 或 CSS。只能透過 VS Code 提供的 API (TypeScript) 去定義「這裡有幾個節點」、「這個節點的文字是什麼」、「旁邊要放什麼原生的 Icon」。
優點:極度省資源、啟動瞬間完成。它甚至無法放普通的輸入框 (input) 或自訂大按鈕 (button),只能依賴 VS Code 原生的行為(如點擊節點觸發指令、點選單按鈕等)。
以下做一個語法範例對比,同樣做一個「輸入名稱、點按鈕就打 API」的功能:
範例:使用 Webview 的方式 (前端發送)
Webview 的寫法就像平常寫前端網頁,UI 是畫出來的,並使用標準的 fetch API(需注意可能會遇到 CORS 跨網域阻擋問題):
<!-- Webview 中的 HTML -->
<input type="text" id="nameInput" placeholder="請輸入名稱" />
<button id="submitBtn">送出名稱並呼叫 API</button>
<div id="result" style="margin-top: 10px; color: var(--vscode-editor-foreground);"></div>
<script>
const vscode = acquireVsCodeApi();
document.getElementById('submitBtn').addEventListener('click', async () => {
const val = document.getElementById('nameInput').value.trim();
const resultDiv = document.getElementById('result');
if (!val) {
resultDiv.innerText = "請先輸入名稱!";
return;
}
resultDiv.innerText = "⏳ 載入中...";
try {
// 在前端 Webview 直接發送 Ajax (fetch) 請求
const response = await fetch('https://api.example.com/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: val })
});
if (!response.ok) {
throw new Error(`伺服器錯誤 (HTTP ${response.status})`);
}
const data = await response.json();
resultDiv.innerText = `✅ 成功:伺服器回傳 ${data.message}`;
// (選擇性) 將成功結果回報給 Extension Host 進行後續檔案處理
vscode.postMessage({ command: 'apiSuccess', result: data });
} catch (error) {
// 例外錯誤處理
resultDiv.innerText = `❌ 發生錯誤: ${error.message}`;
console.error('API 請求失敗:', error);
}
});
</script>
使用原生 TreeView 與 VS Code API (後端 Extension Host 發送)
TreeView 裡面無法放入 <input>。如果需要使用者輸入文字,必須呼叫 VS Code 頂部的「中央浮動輸入框」。由於是由 Extension Host (Node.js 環境) 發起網路連線,最大的好處是完全不會有 CORS 跨網域限制。
// 在 extension.ts 主程式中
// 1. 建立一個按鈕指令 (綁定在 TreeView 節點或標題列上)
vscode.commands.registerCommand('myExtension.askNameAndFetch', async () => {
// 2. 呼叫「原生輸入對話框」 (它會從 VS Code 畫面上方彈出來)
const userInput = await vscode.window.showInputBox({
prompt: '請輸入要送出的名稱',
placeHolder: '例如: 我的新產品'
});
if (!userInput || userInput.trim() === '') {
return; // 使用者按下了取消對話框或沒輸入內容
}
// 3. 貼心的體驗:在 VS Code 的右下角顯示帶著轉圈圈的進度提示
vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: "正在發送請求到伺服器...",
cancellable: false
}, async (progress) => {
try {
// 在 Node.js 擴充主環境 (Extension Host) 發送 API 請求 (最新版 Node 支援原生 fetch,若是舊環境可以使用 axios)
const response = await fetch('https://api.example.com/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: userInput })
});
if (!response.ok) {
throw new Error(`伺服器回應異常 (HTTP ${response.status})`);
}
const data = await response.json();
// 成功:跳出標準的右下角綠色打勾提示視窗
vscode.window.showInformationMessage(`✅ 成功:伺服器回傳 ${data.message}`);
} catch (error: any) {
// 例外錯誤處理:跳出標準的右下角紅色錯誤提示視窗
vscode.window.showErrorMessage(`❌ API 請求失敗: ${error.message}`);
console.error('連線出錯:', error);
}
});
});
如果套件需要提供豐富的表單、圖表、拖曳功能,必須使用 Webview,但要注意跨網域存取的問題。
但如果套件只是列出一堆指令清單,點擊後觸發擴充主程式執行 API 請求,那請毫不猶豫地選擇 TreeView 加上 showInputBox 與 withProgress 等原生 API,這會讓的擴充套件完美融入編輯器,並且避開瀏覽器諸多煩雜的限制!
VS Code Webview 跟一般網頁的不同
上面都在說 Extension 開發邏輯的不同,那如果就鐵了心要在 Webview 裡面大寫特寫 HTML/JS/CSS 呢?
請注意,雖然 Webview 裡面就像一個小瀏覽器,但它為了確保編輯器的穩定性與安全性,被閹割了許多原生的瀏覽器行為。如果用常規寫網頁前端、Node.js 後端的思維去寫 Webview,保證會踩雷:
CSS 支援度與預設樣式
Webview 支援絕大部分現代 CSS,版本號並沒有跟 Chrome stable 差太多,但 VS Code 預設就會在 Webview 注入一堆基礎樣式(例如 body 預設就沒有外距,字體已被強制設定為等寬或跟隨編輯器的主題字體)。
html 與 body 的 height 行為可能跟一般瀏覽器有落差,不要輕而易舉地依賴全螢幕 vh 或全版 fixed 屬性,特別是在多層嵌套的 Sidebar Webview 內,很容易會有捲軸出錯或內容被切斷的問題。
恐怖的對話框:alert(), confirm(), prompt()
在一般的 JavaScript 裡面,可能習慣呼叫 alert('哈囉')。但在 Webview 裡面呼叫傳統的原生彈跳視窗是無效的,甚至可能會拋出錯誤並靜默失敗!
VS Code 嚴格禁止 Webview 彈出這種會強行阻塞執行緒 (Thread-blocking) 的系統對話框。如需提示使用者什麼事情出錯了,請一律透過
postMessage,請後端 Extension Host 呼叫原生的 vscode.window.showInformationMessage 這種非阻塞 toast style 通知。
好笑的問題又來了,我用的是比例 21:9 的寬螢幕,一開始用 vscode.window.showInformationMessage 還以為程式壞了,怎麼按都沒反應,因為眼睛盯著左邊側邊欄,而那個通知則是小小默默的在視窗最右邊...。
如果希望警告能像真正的 Dialog 一樣強迫使用者注意到並阻斷操作,可以在呼叫時傳入 { modal: true } 參數,它就會從視窗正中央彈出來,迫使使用者必須點擊按鈕才能關閉:
// 在 Extension Host (後端) 中執行:
vscode.window.showInformationMessage('這是一個會出現在畫面正中央的對話框!', { modal: true });
令人混淆又驚豔的原生元件體驗
難免會遇到需要讓使用者選取顏色的情境,例如其中一個功能是在圖片上畫色塊,那令人好奇的事又來了,它會顯示 os 層級的調色盤,還是 chromium 的那種調色盤?
在 Webview 裡直接使用原生的 HTML5 <input type="color">,因為 VS Code 底層是基於 Electron(也就是 Chromium 核心),所以彈出來的並不是傳統小畫家或 Office 詳細色彩裡的那種 Windows 經典原生選色器,而是 Google Chrome / Chromium 內建的那個現代風格調色盤。

這個細節展示了 VS Code (Electron) 在處理底層 HTML 標籤時的有趣現象:很多地方就像開發網頁程式,繼承了 Chrome 的許多預設行為,但有些地方又不是那麼回事。
取得使用者電腦硬體與系統資訊
既然我們知道 VS Code 的 Extension Host 是一個滿血的 Node.js 環境,所以我們完全有能力取得使用者電腦的底層資訊,這是一般瀏覽器、webp app絕對做不到的。
甚至可以取得使用者的環境變數,然後把字串全部傳到別的地方去。
基本系統資訊
在 extension.ts (後端) 中,可以直接 import * as os from 'os';,它能提供許多系統資訊:
- 作業系統平台:
process.platform結果會印出例如'win32'(Windows)、'darwin'(macOS) 還是'linux'。 - 電腦名稱:
os.hostname()可以取得電腦名稱 (例如 USER-PC)。 - CPU 架構與型號:
os.arch()(如 x64, arm64),以及os.cpus()會回傳一個陣列,裡面詳細列出每一顆 CPU 核心的型號名稱 (如 "Intel(R) Core(TM) i7...") 和運作時脈。 - 記憶體資訊:
os.totalmem()(總 RAM) 和os.freemem()(可用 RAM)。 - 網卡 MAC Address:
os.networkInterfaces()會回傳所有網卡的詳細資訊,包含內網 IP 和 MAC 位址 (屬性為mac)。
比較特別的是 os 模組無法直接撈到 GPU 的型號,如果需要知道裝置的顯示卡,可以利用 child_process 呼叫系統指令,例如在 Windows 上,可以用 child_process.exec 去執行 WMI 指令 (例如 wmic path win32_VideoController get name) 來抓取顯卡名稱。在 Mac 上可以呼叫 system_profiler SPDisplaysDataType。或是使用 systeminformation 之類的第三方套件。
var cp = require('child_process');
var os = require('os');
var platform = os.platform();
var command = '';
if (platform === 'win32') {
command = 'wmic path win32_VideoController get name';
}
else if (platform === 'darwin') {
command = 'system_profiler SPDisplaysDataType | grep "Chipset Model"';
}
else if (platform === 'linux') {
command = 'lspci | grep VGA';
}
環境變數 (Environment Variables) 的風險
除了電腦名稱和網卡 MAC 位址,更危險的是系統的環境變數 (process.env)。有些開發者如果是直接把應用程式佈署在 OS,並習慣將重要的開發金鑰(例如 AWS Access Key、Claude API Key、OpenAI Key 等)直接設定在作業系統的全域環境變數中,方便各個專案共用? 那有福了。
這代表任何安裝在 VS Code 內的擴充套件,只要具備基礎的 Node.js 執行權限(通常都有),都能在背景輕易讀取電腦上的所有環境變數!

如果安裝到了來路不明的惡意套件,它完全可以默默地將 process.env 中的金鑰透過 AJAX fetch() 或 https 傳送到套件作者的伺服器上。
因此:
- 不要輕易安裝不知名、下載量極低的擴充套件、來路不明的 vsix 檔案。
- 對於極度敏感的金鑰,建議盡量寫在專案目錄下的
.env檔案中,並加入.gitignore,而不是設定在作業系統的「全域」環境變數內。(一般套件不會隨便去讀取、解析每個專案的.env,但真要撈的話卻是輕而易舉的事)。 - 若擴充套件本身需要儲存使用者的金鑰,請務必且強烈建議使用 VS Code 官方提供的 SecretStorage API (
context.secrets) 來存取,它會將金鑰加密儲存在作業系統的安全儲存區(如 Windows 認證管理員、macOS 鑰匙圈)中,大幅降低洩漏風險。
實作上,整個模式基本就是 Extension Host 裡用 os 模組,抓好電腦名稱或網卡 MAC,執行某些指令,再透過 postMessage 送到 Webview 印出來。
因為這個擴充套件只是個人自用,所以沒有走審核上架流程,所以不知道例如做一些偷幹資料的功能,但把程式碼混淆,會不會被退件拒絕上架。
不要用 User-Agent 字串判斷平台
如果依賴 navigator.userAgent 來判斷使用者現在是 Windows 還是 Mac,在 Webview 裡拿到這串字串有時候會混雜了 Electron、Code 等奇怪的自訂標記,而且不一定能精確反映底層的真實 OS 狀況。
真要判斷 OS 環境,例如用來顯示不同的鍵盤快速鍵或指令之類的,應該在 Node.js 主程式用 process.platform (會是 win32 之類的),透過變數丟給前端 Webview 使用才最準確。
另外還有一個 vscode.env,它並不是用來判斷作業系統(裡面沒有 os 屬性),而是用來取得 VS Code 編輯器本身的環境狀態。
直接把它整包印出來會失敗(或是印不完整),因為裡面包含了像 clipboard(剪貼簿操作)或 openExternal(開啟外部網址)這種「函數與複雜物件」。但如果去讀取它裡面的純文字/布林值屬性,會發現很多實用的寶藏:
appName: 編輯器名稱 (例如 "Visual Studio Code"、"Cursor"、"Antigravity")language: 使用者的介面語系 (例如 "zh-tw")machineId: 綁定這台電腦的唯一識別碼remoteName: 如果使用者是用 Remote SSH 或 Codespaces 連線,這裡會有遠端機器的名稱;如果是本機開發則是undefined。uiKind: 看現在是跑在桌面版 (Desktop) 還是網頁版 (Web, 例如 vscode.dev) 編輯器中。
擴充套件簡介頁面 (Marketplace Intro) 與 Icon 設定
當擴充套件打包發布,或是在 VS Code 側邊欄點選套件時,都會看到一個「擴充套件簡介頁面 (Extension Details)」(如下圖)。

若要讓這個介面看起來專業且功能介紹完整,有幾個關鍵的設定檔案必須同步更新:
README.md (詳細介紹與圖文)
套件的首頁 (Details Tab) 內容,完全來自專案根目錄下的 README.md。VS Code 會自動將這份 Markdown 檔案渲染成 HTML 顯示在簡介頁面。
每當新增功能,一定要回頭更新這份 README。這能確保使用者了解最新版本有什麼新亮點。
CHANGELOG.md (版本更新紀錄頁籤)
除了首頁的 Details 頁面,VS Code 的套件介紹其實還內建了 Changelog 這個專屬頁籤。
VS Code 不是自己去撈 GitHub repo 的 commit 紀錄,而是非常單純地讀取在專案根目錄底下的 CHANGELOG.md 檔案。
如果將更新記錄全寫在 README.md 裡,使用者在 Changelog 頁籤只會看到一片空白。最佳實務是將詳細的功能介紹留在 README,並且將每個版本的具體修正 (Added/Fixed/Changed) 規矩地寫在 CHANGELOG.md 裡面。打包 .vsix 時,這兩個檔案都會自動被包含進去。
package.json 的 icon 設定
螢幕空間有限,所以一個功能在介面上通常只會看到一顆 icon,一個套件裡面有 N 處要設定,如果套件中途換 icon,總會發現有遺漏沒改到的。
預設的擴充套件介面在左側 Activity Bar 用了 $(file-media) 或 $(gear) 這類的 VS Code 內建的 Codicons 圖標,它在側邊欄會完美顯示。
但是在擴充套件詳細介面(或 Marketplace 商城)上,必須提供一張圖片檔:
- 格式要求:必須是一張解析度至少為
128x128或256x256以上的圖片 (支援 PNG 或 SVG)。 - 設定方式:在根目錄建立
images/icon.png後,於package.json第一層加上一筆"icon": "images/icon.png"。 - 設計建議:為了與 Activity Bar 圖示保持一致體驗,建議將原本的線條 Icon 加上單色背景,以免毫無一致性,使用者難認出這是什麼套件。
測試擴充套件
我每次改完程式碼,是不是都要重新打包成 .vsix 檔案,再反覆安裝、解除安裝來測試?
開發途中需要用一些莫名其妙的方法,例如每次要先把穩定版本移除,才能測試開發中的版本?
答案是:完全不用!
微軟提供了一套極度親切的 hot reload 機制,我們在開發階段,只需要打開 vscode 套件的專案資料夾,按下鍵盤 F5 即可,這會自動編譯,並自動開啟一個新的 VS Code 視窗,可以即時測試擴充套件。

當套件程式碼有修改,也不需要重新打包成 .vsix 檔案,再反覆安裝、解除安裝來測試。只要按下上圖中的 reload 按鈕,變更就會立刻生效!
這個新視窗通常會顯示 Extension Development Host,只有這個視窗可以用到新版本的套件,其他視窗都還是本來安裝的版本。這樣看起來不會弄髒原本真正工作用的 VS Code 環境。
印出除錯資訊
寫網頁前端時,有時候會用 console.log 來印出訊息除錯,但在 VS Code 擴充套件的世界裡,前後端的除錯訊息會出現在不同的地方:
如果在 Webview 的 HTML/JS 裡寫了 console.log('前端訊息'),它不會出現在 VS Code 預設的終端機或輸出面板裡。
我們必須打開專屬於 Webview 的開發者工具:按下 Ctrl+Shift+P (或 Cmd+Shift+P) 叫出命令選擇區,輸入並執行 Developer: Open Webview Developer Tools (開發人員: 開啟 Webview 開發人員工具)。這會彈出一個和 Chrome F12 一模一樣的開發者工具介面,就能在裡面的 Console 看到熟悉的 console.log 輸出了。
如果是在 extension.ts 等 Node.js 主程式中寫了 console.log('後端訊息'):
- 在開發測試階段 (按 F5 執行時):訊息會印在原本撰寫程式的那個 VS Code 視窗底部的「除錯主控台 (Debug Console)」面板中。
- 正式的日誌輸出 (無需要 F5 也能查閱):建議使用 VS Code 提供的 Output Channel API。
// 建立一個專屬的輸出通道
const outputChannel = vscode.window.createOutputChannel("XXXXX Log");
// 寫入訊息
outputChannel.appendLine("這是一筆除錯日誌");
// (選擇性) 強制讓面板顯示並切換到我們的通道
// outputChannel.show();
這樣使用者就可以從底部面板的「輸出 (Output)」頁籤,並從下拉選單找到「XXXXX Log」來查看套件運作的日誌了。
產生安裝檔案 (vsce package)
只有覺得「開發已經告一段落,要把這個版本永久停留在我的作業系統上」,或者是「我要把這個工具傳給其他電腦安裝」時,才需要打包。
- 打包指令:執行
npx vsce package。 - 這個指令會讀取的
package.json(包含裡面的version,icon等資訊) 與README.md,然後將所有原始碼壓扁打包成一個.vsix檔案(例如my-extension-0.8.0.vsix)。 - 最後才透過 VS Code 擴充套件面板右上角的「從 VSIX 安裝 (Install from VSIX...)」把這個成果真正部署到電腦中。
不要以為能正常建置就沒事,也有可能在安裝或執行時報錯,例如前面所講的跨平台問題。
結語
這算是一個 vibe coding 實驗性質的玩意,網頁設計與程式的技能可以拿來做很多事,不知道為什麼有些人總覺得只能拿來做形象網站、購物網站?
就像幾年前 Flash Player 即將停止支援,各大網頁瀏覽器在整人的時候,本部落格也試玩過一個 Electron 桌面應用程式,Electron 實戰:讓 Mac 玩開心水族箱不用再每次按允許 Flash ,把開心水族箱的 Flash 網頁搬到 PC 桌面應用程式。
以前總覺得開發桌面應用程式的擴充套件門檻很高,還要學很多底層的 API?
但實際動手嘗試後發現,除了傳統的開發方式,如果站在巨人的肩膀上,在 VS Code 裡面執行,只要釐清了 Webview(前端 UI)與 Extension Host(後端 Node.js)之間的通訊機制與沙盒權限邊界,剩下的開發體驗其實就跟寫一般的全端網頁應用程式差不多。
把寫作、修圖、圖檔上傳、甚至終端機指令操作,全部收斂回常常盯著看的 VS Code 介面中,大幅減少了在不同應用程式與瀏覽器分頁間來回切換的心智消耗,讓維護部落格這件事變得流暢純粹了不少。
雖然這個套件只是為了解決自己的痛點而生,有些機制也沒有做得很嚴謹,但能隨心所欲地打造出最符合自己工作流的工具,不被現成平台的框架侷限,這大概就是浪漫與樂趣吧。
如果平時開發或寫作上也有一些繁瑣的重複性操作,不妨也試著寫一個專屬自己的 VS Code 擴充套件試試看!