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 大神的点赞
开发准备
云开控制台: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 个:
调研基本示例
由于需要进行容器部署,所以在 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/std@0.62.0/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!
开源贡献
我们非常欢迎各位开发者为 CloudBase Framework 贡献一份力量,让这个项目能够更好地帮助开发者提升效率。
Github 地址:
参与贡献
- 积极参与 Issue 的讨论,如答疑解惑、提供想法或报告无法解决的错误;
- 撰写和改进项目的文档;
- 提交补丁优化代码;
- 认领待办任务中的事项。
作者:梁栋
tabliang,腾讯高级前端开发工程师。
参考资料
- awesome-deno
- awesome-deno-cn
- Deno 运行时入门教程
- Deno 并不是下一代 Node.js
- 了不起的 Deno 入门与实战
- 通俗易懂的 Deno 入门教程
- Deno 入门手册:附大量 TypeScript 代码实例
- React技巧8(不再手动绑定this,跟.bind(this)说88)
- 美美的商务范儿——ggplot2蝴蝶图
- 机器学习(三)使用Python和R语言从头开始理解和编写神经网络介绍目录神经网络背后的直观知识多层感知器及其基础知识什么是激活函数?前向传播,反向传播和训练次数(epochs)多层感知器全批量梯度下降
- React第三方组件2(状态管理之Refast的使用⑤LogicRender使用)
- 左右用R右手Python9——字符串合并与拆分
- hdu 1003 Max Sum(最大子窜和)
- React第三方组件2(状态管理之Refast的使用④中间件middleware使用)
- 高维分面应用——ggplot2分面气泡图饼图
- poj 1579 Function Run Fun
- React第三方组件2(状态管理之Refast的使用③扩展ctx)
- 财经小知识——CRS风暴与全球离岸金融中心
- The Triangle
- React第三方组件2(状态管理之Refast的使用②异步修改state)
- UVa Automatic Editing
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- JavaWeb - 开发环境搭建和 Shell 编程
- 第四章--第一节:函数
- 教育平台项目后台管理系统:接口文档
- 第四章--第二节:类
- 教育平台项目后台管理系统:介绍与搭建
- Python办公自动化之Excel做表自动化:全网最全,看这一篇就够了!
- Java学习笔记-spring-Bean实例化
- Java学习笔记-spring-Bean作用于
- 教育平台项目后台管理系统:课程信息模块
- 教育平台项目后台管理系统:课程内容模块
- 100 个 Python 小例子
- Entity Framework初体验
- Entity Framework 小知识(一)
- 教育平台项目前端:Vue.js 入门
- Entity Framework 约定