查看原文
其他

不会吧?还有人不会Dfinity前端开发?

ICPLabs ICPLabs 2022-03-21

“日前,Uniswap对某些币种的前端限制引起了巨大争议。不论对错,从技术角度出发,Dfinity相比之下就是一个更好的全栈去中心化解决方案。本篇教程将从HelloWorld开始讲起,详细介绍如何在Dfinity上进行前端开发,以及如何使用官方提供的身份认证服务(identity.ic0.app)。”

PS: 阅读本文前需熟悉 dfx 开发工具的使用 

01从Hello World入手

在任意目录下输入如下命令,即可得到一个初始项目并部署

dfx new helloworlddfx start --background & dfx deploy

在浏览器输入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前端应用程序,了解这些过程就可以进行前后端的交互。

02前端脚手架
  • create-ic-app :create-ic-app使用了新型前端构建工具Vite,支持react、vue、typescript、vanilla等框架,并还在持续更新。另外,Vite的dev server响应速度比webpack快了不少,值得尝试。

  • dfinity-vue :vue脚手架,在一个分支上集成了vuetify,如需使用切换到vuetify分支即可。

  • cra-template-dfx :react脚手架,上次更新是七个月前,仅供参考不推荐使用。

03关于webpack配置和Dev Server

Dfinity项目默认使用webpack打包,并自动生成了配置文件,我们需要做的其实非常少。
在讲webpack之前我们需要打开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" } }}

如何在项目中配置Typescript
首先是安装typescript并配置好tsconfig.json,安装ts-loader,这里不再展开。接下来找到webpack.config.js中这段被注释掉的代码,取消注释,即可在项目中使用typescript。

配置DevServer

事实上,dfx的开发体验差强人意,每当代码出现改动,就需要重新编译部署。这种体验对于前端开发来说如同噩梦,好在,我们还有dev server。


使用如下命令安装webpack-dev-server

npm i webpack-dev-server

安装完成后就可以通过webpack serve或是webpack-dev-server命令启动它,笔者的版本为3.11.2,对应的启动命令是webpack serve。

当然,也可以在package.json中配置dev选项,如图

如此,即可通过输入npm run dev 来启动Dev Server

接下来,在webpack中添加


pluginnew webpack.HotModuleReplacementPlugin()

然后添加dev server配置

devServer: { hot: true, }

即可实现修改文件后自动重新编译

04调用第三方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服务为例


找到对应的Canister

选择javascript,新建一个identity.did.js复制粘贴进去(ts对应identity.did.d.ts)

// src/declarations/identity/index.jsimport { Actor, HttpAgent } from "@dfinity/agent";
// Imports and re-exports candid interfaceimport { idlFactory } from './identity.did.js';export { idlFactory } from './identity.did.js';// CANISTER_ID is replaced by webpack based on node environmentexport 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/didcelif [ ${machine} = "Linux" ]then echo "Downloading didc for Linux to ~/bin/didc" curl -fsSL https://github.com/dfinity/candid/releases/download/${release}/didc-linux64 > ~/bin/didcelse 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.jsdidc bind ./identity.did -t ts > ./identity.did.d.ts //ts版本

就可以生成对应的did.js文件或did.d.ts文件

05如何使用Internet Identity服务


Internet Identity(简称II) 是dfinity官方推出的DID服务,基于WebAuth。在Dfinity上的应用一般会支持使用II登录。

如果想要在本地的开发环境调试II,需要把Ineternet Identity clone 到本地,并部署到你本地用于开发的dfx网络环境中。

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

cd internet-identity


dfx start --clean

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

npm installII_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); }


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存