CloudBase Framework丨第一個 Deno 部署工具是如何打造的?
雲端一體化部署工具 CloudBase Framework (簡稱 CBF)自開源發布以來迭代迅速,不僅支援 Vue、React 等前端框架,也支援 Nuxt 等 SSR 框架,基於 Node 開發的應用如 Express、Koa 等也可以一鍵託管。除此之外,藉助底層 Serverless 雲應用的能力,也可以部署其他後端的應用(PHP、Java、Go 等),值得一提的是可以部署 Dart Server,可以配合 Flutter 實現 Dart 語言的雲端一體化,這也是中國雲廠商對 Dart 語言和生態的一大補充。
現在,CloudBase Framework 已支援部署Deno,可能是首個支援部署Deno的前後端一體化部署工具!下面就來介紹下 Deno 插件的開發流程。
Deno是基於V8引擎和Rust語言所創建的JavaScript、TypeScript運行環境,由Node.js的原始開發者Ryan Dahl所創造,目前 github star 66.7k+。
來自 justjavac 大神的點贊
開發準備
雲開控制台://console.cloud.tencent.com/tcb
當 CloudBase Framework 正式推出後,一直覺得 Deno 和雲開發應該是絕配,所以嘗試為其貢獻了 Deno 插件與模板,並調研感受了下 Deno 開發過程。
相關產出:
- cloudbase-framework deno 插件 framework-plugin-deno
- 簡易在線示例
- 簡易在線示例程式碼 deno 模板
開始著手 deno 插件開發時,CloudBase Framework 插件開發的文檔暫缺,不過好在其他插件程式碼清晰易懂,可以參考其他插件進行開發。
考慮到 deno 運行狀態,應該就是需要打通容器部署環節,於是根據 CloudBase Framework 作者建議,參考了 framework-plugin-node 和 framework-plugin-dart 兩款插件的程式碼來進行開發。
整個 CloudBase Framework deno 插件開發,主要需要編寫程式碼的文件就 3 個:
調研基本示例
由於需要進行容器部署,所以在 dockerhub 找了個 docker image aredwood/deno 作為參考鏡像進行改造。來編寫 CloudBase Framework 插件所需 的 Dockerfile 。
為方便驗證 Dockerfile 和 deno 應用如何整合,構建了一個簡單項目來驗證鏡像構建流程:deno-docker
deno 生態有一個類似 node koa 的應用框架 oak 直接使用它的官方示例,存為一個 entry.ts
,很快就完成了本地示例的搭建。執行示例也非常簡單 deno run entry.ts
。
插件開發
接下來考慮如何部署的問題,開始開發 CloudBase Framework deno 插件,src/index.ts
主要需要提供一個插件類給 CloudBase Framework 命令行組件使用。這個類需要繼承自 @cloudbase/framework-core
的 Plugin。
參考其他插件寫法,Plugin 是抽象類,需要自行實現抽象類的各個方法。其中在 build 方法中,需要構建中間產物,主要是編譯過後的 Dockerfile 和需要包裝到鏡像的文件,然後通過 framework-plugin-container
提供 docker container 構建產物。
import { plugin as ContainerPlugin } from '@cloudbase/framework-plugin-container';
/*** code:other ***/
class DenoPlugin extends Plugin {
/*** code: 初始化處理 ***/
async build() {
// 構建 deno 中間產物
this.buildOutput = await this.denoBuilder.build(
this.resolvedInputs.projectPath || '.',
{ /*** code: 給 buider 提供選項 ***/ }
);
// 提供 containerPlugin 對象
const container = this.buildOutput.containers[0];
this.containerPlugin = new ContainerPlugin(
'container',
this.api,
resolveInputs(
{ localAbsolutePath: container.source },
this.resolvedInputs
)
);
// 構建 container 最終產物
await this.containerPlugin.build();
}
/*** code: other ***/
}
而 deploy 方法看來主要是在部署之後,提供最終部署結果的日誌呈現。參考其他 2 個插件,大部分程式碼改動主要用來做配置項的處理和日誌的區別,整體與其他插件相比,改動不大。
class DenoPlugin extends Plugin {
async deploy() {
/*** code: 日誌處理 ***/
// 實際部署能力調用
await this.containerPlugin.deploy();
await this.denoBuilder.clean();
/*** code: 日誌處理 ***/
}
}
在 src/builder.ts
中,主要擴展 Builder 類,提供中間產物構建方法。其中 build 方法,參考其他插件,給出容器構建所需的固定返回即可。
import { Builder } from '@cloudbase/framework-core';
/*** code: other ***/
export class DenoBuilder extends Builder {
/*** code: 初始化 ***/
async build(localDir: string, options: BuilderBuildOptions) {
/*** code: 選項處理,路徑處理 ***/
// 生成中間產物需要調用的方法
await Promise.all([
this.generator.generate(
path.join(__dirname, '../assets'),
appDir,
spec
),
fs.copy(path.join(projectDir, localDir), appDir),
]);
// 對於容器部署,是固定的返回
return {
containers: [
{
name: containerName,
options: {},
source: appDir,
},
],
routes: [
{
path: options.path,
targetType: 'container',
target: containerName,
},
],
};
}
}
this.generator.generate
方法調用時,Dockerfile 會作為 ejs 模板被進行編譯,傳遞的選項將會作為編譯參數。結合這個能力,可以實現 docker image 的精細配置。
本地部署調試
調試 CloudBase Framework deno 插件時,需參考 cloudebase-framework 貢獻指南 提供的本地調試流程。
本地需要部署的程式碼,需要提供一個 cloudbaserc.json
作為部署配置。如果是開發模板,需要配置屬性 "envId": "{{envId}}"
。cloudbaserc.json
參考 CloudBase Framework 配置文檔 來配置屬性。其中 inputs 屬性將作為參數傳遞給插件。
以我個人模板調試為例,插件編寫完畢後,需要在插件目錄執行 npm run build
編譯插件程式碼。然後在 cloudbase-framework 根目錄執行 npm run link
實現插件的本地指向。最後在模板目錄執行 CLOUDBASE_FX_ENV=dev cloudbase framework:deploy -e test-1gxe3u9377a09734
來進行部署。
test-1gxe3u9377a09734 為我個人的 envId,將會替換 cloudbaserc.json
中的 “{{envId}}” 部分。
deno 開發體驗
開發
deno 可以直接運行 typescript,示例程式碼跑在開發模式,報錯時可以直接看到清晰的調用棧,這彌補了 typescript 在 node 開發中的弊端。好感度 +1 !
部署
初次部署時經常碰到部署失敗,經過溝通與調試,發現問題主要出在 docker image 編譯和 app 應用執行環節中,由於網路環境問題,部分遠程文件未能成功載入或者快取。
再次審視 deno 項目介紹與說明,發現最佳實踐是進行本地打包(或者 ci 打包)後提供無依賴的入口文件。
deno 提供了 deno bundle
命令,可以將程式碼打包為一個 js 文件來執行。然後找到 denon 這個工具,直接解決了開發部署配置問題,其類似 nodemon
。舒服的是,包括 deno 應用的執行許可權,環境變數,都可以在它的配置文件中配置。所以直接修改了 CloudBase Framework deno 插件,使用 denon 來提供啟動應用能力。
使用先打包,後部署的方案後,雲開發部署 deno 應用的成功率大幅上升。
依賴
值得一提的是,雖然示例應用簡陋,但是依然能感受到 deno 打包執行流暢易用。好感度 +1!
脫離了 node_modules 這層設計,deno 內置的打包部署這方面的體驗遠超 node 開發。本地應用開發設計時,推薦使用固定版本的文件引用方式,這樣可以避免依賴更新導致的應用 bug。
/* @see //github.com/oakserver/oak/blob/main/application.ts */
import { reset } from "//deno.land/[email protected]/fmt/colors.ts";
模板引擎
在使用 dejs 模板時,發現示例中的 cwd()
不能使用。
(async () => {
const output = await renderFile(`${cwd()}/views/main.ejs`);
await copy(output, stdout);
})();
需要改為 Deno.cwd()
:
(async () => {
const output = await renderFile(`${Deno.cwd()}/views/main.ejs`);
await copy(output, stdout);
})();
而嵌套模板程式碼直接報錯,只提示文件未找到,卻並未給出更詳細提示。
<%- await include('views/header.ejs') %>
<h1>hello, world!</h1>
<%- await include('views/footer.ejs') %>
反覆調試後發現,需改為:
<%- await include(`${Deno.cwd()}/views/header.ejs`) %>
<h1>hello, world!</h1>
<%- await include(`${Deno.cwd()}/views/footer.ejs`) %>
IO
在 deno 應用中,使用 fetch 方法獲取遠程資源時,該方法與瀏覽器規範實現一致,使用起來莫名親切。由於 deno 默認直接讀取了環境變數的 http_proxy,node 開發中碰到的內網代理配置問題,在 deno 開發中也不再存在。好感度 +1 !
總結
聯繫到 Deno 的願景是設計一款服務端運行的瀏覽器,忽然有了一些大膽的想法,想來在 SSR、測試、Web資源編輯與創建方面,Deno 未來可能會有一些獨到的優勢。
總體來說,即便 Deno 並非 Node 的替代者,依靠其順滑的開發部署體驗,未來極有可能分走 Node 相當一部分使用場景。而這個項目在 github 上的 star 數量,與社區參與人數的快速上漲,也證明其具有相當大的潛力。
Deno is coming!
參與貢獻
- 積极參与 Issue 的討論,如答疑解惑、提供想法或報告無法解決的錯誤;
- 撰寫和改進項目的文檔;
- 提交修補程式優化程式碼;
- 認領待辦任務中的事項。