title: Dfinity 前端开发教程#
date: 2021-08-05 15:58:32
Dfinity 前端开发教程#
作者 转载请注明出处
前段时间 Uniswap 对某些币种的前端限制引起了很大的争议,Dfinity 是一个很好的全栈去中心化解决方案,本篇教程从 dfx new helloworld 开始讲起,详细的介绍了如何在 dfinity 上进行前端开发,并讲解了如何使用官方提供的 identity 服务(identity.ic0.app),一起来加入去中心化前端开发的世界吧!
阅读本文前需熟悉 dfx 开发工具的使用
教程目录:#
-
从 hello world 入手
-
前端脚手架
-
关于 webpack 配置和 dev server
-
如何调用第三方 canister
-
如何使用 Internet Identity 服务
从 hello world 入手#
在任意目录下输入dfx new helloworld
, 你会得到一个初始项目。运行dfx start & dfx deploy
在浏览器输入前端id.localhost:8000
(推荐) 或 localhost:8000/?canisterId=前端id
可以得到类似于下图的界面
js 代码如下
import { Actor, HttpAgent } from "@dfinity/agent";
import {
idlFactory as helloworld_idl,
canisterId as helloworld_id,
} from "dfx-generated/helloworld";
const agent = new HttpAgent();
const helloworld = Actor.createActor(helloworld_idl, {
agent,
canisterId: helloworld_id,
});
document.getElementById("clickMeBtn").addEventListener("click", async () => {
const name = document.getElementById("name").value.toString();
const greeting = await helloworld.greet(name);
document.getElementById("greeting").innerText = greeting;
});
这段代码先是创建了一个 HttpAgent,用于发送网络请求。这里 agent 默认的 host 是你网络环境的地址,例如你在 localhost:8000 部署了前端,这里 agent 就会向 localhost:8000 发送请求。所以当你使用 dev server 时一定要注意手动设置 host,下文中会讲解 dev server 的使用。
如下是设置 host 的代码(生产环境中不要使用)
const agent = new HttpAgent({
host: "http://localhost:8000",
})
因为 dfinity 中的 canister 使用的 Actor 模型,前端在调用 canister 时需要先创建一个 actor,这里你可以简单理解为调用后端的工具。
接下来的就是创建 actor 的代码,这里需要三个参数,IDL,agnet,canisterId,agent 就是我们刚才所创建的 HttpAgent,另外两个参数将在如何调用第三方 canister中介绍
当我们完成了 actor 创建后就可以通过 actor 对后端的 public method 进行调用,注意这里的调用都是异步调用。对于异步调用的处理本篇教程就不再展开,比较推荐的方法是使用 async/await 语法。这段代码调用了 greeting 方法并返回了一段文本。
以上就是一个最简单的 dfinity 前端应用程序,了解了这些过程就可以进行前后端的交互。
前端脚手架#
我们使用 vue,react 等框架进行前端开发时,一般会使用脚手架。dfinity 社区中有许多可以使用的脚手架,但目前大多处于早期阶段,使用起来可能会遇到一些问题
create-ic-app
create-ic-app 使用了新型前端构建工具 Vite,支持 react、vue、typescript、vanilla 等,并还在持续更新,Vite 的 dev server 用起来比 webpack 快很多,推荐尝试
dfinity-vue
vue 脚手架,在一个分支上集成了 vuetify,如需使用切换到 vuetify 分之即可
cra-template-dfx
react 脚手架,上次更新是七个月前,仅供参考不推荐使用
关于 webpack 配置和 dev server#
dfinity 项目默认使用 webpack 打包,并自动生成了配置文件,我们需要做的其实非常少。
在讲 webpack 之前我们需要打开 dfx.json 来了解一下默认配置,如果你经常使用 dfx 这个工具对于 dfx.json 应该很熟悉,不过这里还是简单讲一下
{
"canisters": {
...
//assets 是前端canister的名字,在dfinity中所有的代码都部署在canister中
"assets": {
//前端canister的dependency
"dependencies": [
"backend"
],
//frontend.entrypoint是前端文件的entry point,这个我们要在webpack中用到
"frontend": {
"entrypoint": "dist/index.html"
},
//source指定dist和src目录
"source": [
"dist/"
],
//指定canister的类型为前端canister
"type": "assets"
}
}
}
如果你用过 dfx 就会知道它的开发体验是非常差的,每改一下代码就要重新编译部署,对于前端开发来说我们一定是需要一个 dev server
安装 webpack-dev-server 直接输入npm i webpack-dev-server
安装完成后就可以通过 webpack serve
或者是 webpack-dev-server
命令启动 Dev Server, 我的版本是 3.11.2 , 需要使用 webpack serve
启动。
也可以在 package.json 中配置 dev 选项
这样就可以输入npm run dev
启动 Dev server
在 webpack 中添加 pluginnew webpack.HotModuleReplacementPlugin(),
然后添加 dev server 配置
devServer: {
hot: true,
}
就能实现修改文件后自动重新编译
如何调用第三方 canister#
内容参考kyle 的博客
调用 canister 需要两个参数,一个 IDL 接口描述,一个 canister ID
IDL 是对 canister 数据类型和接口的描述,在本地部署 canister 时会自动生成一个 canister.did.js 文件
export default ({ IDL }) => {
return IDL.Service({ 'greet' : IDL.Func([IDL.Text], [IDL.Text], []) });
};
export const init = ({ IDL }) => { return []; };
hello world 的 IDL 长这样
import { idlFactory } from "dfx-generated/helloworld";
或
import { idlFactory } from './helloworld.did.js';
导入 IDL
如果是第三方 canister 你可以到 ic.rocks 找到对应的 IDL 文件,以 identity 服务为例
选择 javascript,新建一个 identity.did.js, 复制粘贴进去(ts 是 identity.did.d.ts)
// src/declarations/identity/index.js
import { Actor, HttpAgent } from "@dfinity/agent";
// Imports and re-exports candid interface
import { idlFactory } from './identity.did.js';
export { idlFactory } from './identity.did.js';
// CANISTER_ID is replaced by webpack based on node environment
export const canisterId = process.env.IDENTITY_CANISTER_ID;
/**
*
* @param {string | import("@dfinity/principal").Principal} canisterId Canister ID of Agent
* @param {{agentOptions?: import("@dfinity/agent").HttpAgentOptions; actorOptions?: import("@dfinity/agent").ActorConfig}} [options]
* @return {import("@dfinity/agent").ActorSubclass<import("./identity.did.js")._SERVICE>}
*/
export const createActor = (canisterId, options) => {
const agent = new HttpAgent({ ...options?.agentOptions });
// Fetch root key for certificate validation during development
if(process.env.NODE_ENV !== "production") {
agent.fetchRootKey().catch(err=>{
console.warn("Unable to fetch root key. Check to ensure that your local replica is running");
console.error(err);
});
}
// Creates an actor with using the candid interface and the HttpAgent
return Actor.createActor(idlFactory, {
agent,
canisterId,
...options?.actorOptions,
});
};
/**
* A ready-to-use agent for the identity canister
* @type {import("@dfinity/agent").ActorSubclass<import("./identity.did.js")._SERVICE>}
*/
export const identity = createActor(canisterId);
创建一个用于调用 identity 服务的 actor
如果只有 candid 文件,可以通过 dfx 工具生成一份 did.js,不过这种方式比较繁琐,还有一种更简单的方式是通过官方提供的didc 工具
自动安装脚本:(复制保存为 sh 文件运行)
unameOut="$(uname -s)"
case "${unameOut}" in
Linux*) machine=Linux;;
Darwin*) machine=Mac;;
*) machine="UNKNOWN:${unameOut}"
esac
release=$(curl --silent "https://api.github.com/repos/dfinity/candid/releases/latest" | grep -e '"tag_name"' | cut -c 16-25)
if [ ${machine} = "Mac" ]
then
echo "Downloading didc for Mac to ~/bin/didc"
curl -fsSL https://github.com/dfinity/candid/releases/download/${release}/didc-macos > /usr/local/bin/didc
elif [ ${machine} = "Linux" ]
then
echo "Downloading didc for Linux to ~/bin/didc"
curl -fsSL https://github.com/dfinity/candid/releases/download/${release}/didc-linux64 > ~/bin/didc
else
echo "Could not detect a supported operating system. Please note that didc is currently only supported for Mac and Linux"
fi
date
安装完成后输入didc bind ./identity.did -t js > ./identity.did.js
就可以生成 did.js 文件 (didc bind ./identity.did -t ts > ./identity.did.d.ts
ts 版本)
如何使用 Internet Identity 服务#
Internet Identity (简称 II) 是 dfinity 官方推出的 DID 服务,基于 WebAuth。在 dfinity 上的应用一般会支持使用 II 登录。
如果想要在本地的开发环境调试 II,需要把Ineternet Identity clone 到本地,并部署到你本地用于开发的 dfx 网络环境中
git clone [email protected]:dfinity/internet-identity.git
cd internet-identity
dfx start --clean
这里推荐使用 --clean 参数启动,因为 II 服务的默认 canister id 与本地钱包的默认 id 相同,所以先清扫一下环境避免冲突。注意因为本地能启动多个 dfx 网络,一定要保证 II 部署的网络与项目的网络相同(端口相同)
npm install
II_ENV=development dfx deploy --no-wallet --argument '(null)'
这样就完成了 II 的本地部署
打开 package.json 确保你安装了 @dfinity/auth-client、@dfinity/authentication
下面是前端调用 II 服务的代码
const init = async () => {
authClient = await AuthClient.create();
await authClient.login({
maxTimeToLive: BigInt("0x7f7f7f7f7f"),
identityProvider:
"http://localhost:8000/?canisterId=rwlgt-iiaaa-aaaaa-aaaaa-cai"
onSuccess: async () => {
handleAuthenticated(authClient);
},
});
}
这段代码同样非常容易理解,先是创建 authclient,然后调用 login 方法。
maxTimeToLive 是设置委托密钥的有效时间,identityProvider 就是 II 部署到的 canister,如果在主网的话就是 identity.ic0.app,onSuccess 是认证完成后的回调函数。这里还有很多参数可以指定,具体可以查看Agent JS Docs,不再展开。
下面讲一下如何发起认证过的调用,前面 helloworld 的例子中讲过如何发起调用,不过使用的是随机生成的匿名身份。当用户完成认证后我们就能获取到他们的身份,如何使用用户的身份发起认证过的调用呢,如下,在创建 agent 时传入 identity 即可
async function handleAuthenticated(authClient) {
identity = await authClient.getIdentity();
const agent = new HttpAgent({
identity: identity,
host: "http://localhost:8000",
});
//本地开发时需要获取Root Key
if(process.env.NODE_ENV !== "production") await agent.fetchRootKey();
let actor = Actor.createActor(idlFactory, {
agent,
canisterId: canisterId,
});
const greeting = await actor.greet(name);
}