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 代码如下

Copy
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 的代码（生产环境中不要使用）

Copy
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 应该很熟悉，不过这里还是简单讲一下

Copy
{
"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 配置

Copy
devServer: {
hot: true,
}

就能实现修改文件后自动重新编译

如何调用第三方 canister

内容参考kyle 的博客

调用 canister 需要两个参数，一个 IDL 接口描述，一个 canister ID

IDL 是对 canister 数据类型和接口的描述，在本地部署 canister 时会自动生成一个 canister.did.js 文件

Copy
export default ({ IDL }) => {
return IDL.Service({ 'greet' : IDL.Func([IDL.Text], [IDL.Text], []) });
};
export const init = ({ IDL }) => { return []; };

hello world 的 IDL 长这样

Copy
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)

Copy
// 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 文件运行)

Copy
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 网络环境中

Copy
git clone git@github.com:dfinity/internet-identity.git
cd internet-identity

Copy
dfx start --clean

这里推荐使用 --clean 参数启动，因为 II 服务的默认 canister id 与本地钱包的默认 id 相同，所以先清扫一下环境避免冲突。注意因为本地能启动多个 dfx 网络，一定要保证 II 部署的网络与项目的网络相同（端口相同）

Copy
npm install
II_ENV=development dfx deploy --no-wallet --argument '(null)'

这样就完成了 II 的本地部署

打开 package.json 确保你安装了 @dfinity/auth-client、@dfinity/authentication

下面是前端调用 II 服务的代码

Copy
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 即可

Copy
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);
}