天天看點

CloudBase Framework丨第一個 Deno 部署工具是如何打造的?

現在,CloudBase Framework 已支援部署Deno,可能是首個支援部署Deno的前後端一體化部署工具!

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 部署工具是如何打造的?

現在,CloudBase Framework 已支援部署Deno,可能是首個支援部署Deno的前後端一體化部署工具!下面就來介紹下 Deno 插件的開發流程。

Deno是基于V8引擎和Rust語言所建立的JavaScript、TypeScript運作環境,由Node.js的原始開發者Ryan Dahl所創造,目前 github star 66.7k+。

CloudBase Framework丨第一個 Deno 部署工具是如何打造的?
來自 justjavac 大神的點贊

開發準備

雲開控制台:https://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 個:

  • assets/Dockerfile
  • src/index.ts
  • src/builder.ts

調研基本示例

由于需要進行容器部署,是以在 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 https://github.com/oakserver/oak/blob/main/application.ts */
import { reset } from "https://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 數量,與社群參與人數的快速上漲,也證明其具有相當大的潛力。

CloudBase Framework丨第一個 Deno 部署工具是如何打造的?

Deno is coming!

參與貢獻

  • 積極參與 Issue 的讨論,如答疑解惑、提供想法或報告無法解決的錯誤;
  • 撰寫和改進項目的文檔;
  • 送出更新檔優化代碼;
  • 認領待辦任務中的事項。