随遇而安的DAPP开发实践教程(二)使用HTML作为DAPP前端

1、一个带有用户界面的案例

在上一篇教程《随遇而安的DAPP开发实践教程(一)搭建一个可用的开发环境》当中,我们基于Truffle官方提供的MetaCoin例子进行了初步的开发实践和代码剖析。我们甚至已经可以基于JavaScript编写合约的测试代码并检查结果,看起来一切顺利?

不过值得注意的是,我们是使用“truffle.cmd test”来执行.js文件中的代码的,如果直接使用NodeJS或者浏览器打开,我们马上就会收到成山的错误提示。显然,这是因为JS程序代码中缺少Truffle相关的导入库文件,导致大量方法无法找到出处所致。

很显然,作为一个完整的可向公众发布的DAPP,我们希望它能够在浏览器中结合前端界面的代码,再伺机调用后端的智能合约接口,与私有或者公开的区块链体系对接。仅仅是测试用的代码完全无法满足这样的要求。

Truffle满足了我们对于前端用户界面的迫切需求,同样是通过它自带的例子WebPack。我们只需要仿照之前下载MetaCoin的方式,在控制台中输入:

# mkdir WebPack

# cd WebPack

# truffle.cmd unbox webpack

这次等待的时间会更长一些,事实上,这是因为前端代码与Truffle合约的通讯需要大量依赖库所致。下载后的目录中多了不少新的文件,它们与合约的编写和发布并没有直接关系。这里的node_modules子目录保存了所有需要用到的第三方依赖库,包括提供了最新的JS语言特性的Babel,代码检查工具ESLint,以及最重要的以太坊专用的通讯接口Web3。而app子目录中保存了网页端的界面代码,CSS样式和JS脚本。工程根目录新增的package.json文件主要被NodeJS和npm用来查找和更新依赖库,以及查找必需模块。

之后的工作和前文所述并无区别,开启Ganache测试链之后,还是依次compile和migrate,即在WebPack目录下,通过控制台依次执行:

# truffle.cmd compile

# truffle.cmd migrate

事实上WebPack工程所涉及的合约代码和MetaCoin没有任何区别,它只是演示了如何给这个非常原始的代币合约案例增加一个图形化的前端而已。不过这也正好让我们有时间去更详细地研究智能合约发布和使用的流程,反省并准备开始从头编写自己的DAPP程序。

现在,我们依然可以通过truffle.cmd test来进行快速的合约函数测试,不过我们已经不满足于此了,我们需要的是在浏览器端看到自己的成果。不过单纯双击app/index.html并不会有什么效果:Chrome等浏览器对于本地运行的JS脚本有各种安全性的限制,例如无法读取外部JSON脚本,Truffle相关的代码可能因而无法正常执行;此外我们也要考虑载入NodeJS的诸多模块。

我们首先要确认前端代码中设置了正确的私有链地址和端口,从前文中我们已经知道,Ganache默认的地址为127.0.0.1:7545,打开app/javascripts/app.js,找到设置地址和端口的代码段,并修改为正确的数值如图:

此时我们也可以顺便看一看app.js中的其它代码段落,了解其中对于智能合约函数sendCoin()和getBalance()的调用方法。简单来说,和上一个教程中的测试JS代码几乎没有什么不同,这对于正摩拳擦掌准备自己编写前端代码的我们来说无疑是一个好消息。

保存退出后,一切看起来还是没有什么头绪,幸好我们有强大的npm。在控制台输入:

# npm run dev

此时会启动开发者模式,自动建立一个本地的服务器端并调用index.html显示前端界面。

根据控制台输出的提示,在浏览器(笔者所用的是Chrome)中输入http://localhost:8080,看看发生了什么?

太神奇了,现在从Ganache的用户列表中随便选择一个地址,在网页上粘贴用户地址之后,再把当前持有的一部分代币分享给他看看。

在Ganache的交易记录中可以找到这笔最新发生的交易,并且我们可以从TX DATA中找到刚才输入的用户地址,以及发送的数额(1000的十六进制为3e8)。

不过带着如此庞大的依赖库终究是巨大的累赘,其中大部分API与当前工程并没有直接关联,甚至还包含了一些对本工程完全无用的案例和文档代码;而且我们在构建自己的网络应用的时候,也绝对不希望用npm来启动服务。不过我们还有另一个指令可以执行,按Ctrl+C退出目前的服务,在控制台中继续输入:

# npm run build

完成后进入build子目录,我们可以发现新生成的index.html和app.js文件,后者的尺寸相比之前显得相当可观(从4KB到1.34MB),显然是把多个依赖库打包合并之后的结果。

很让人兴奋对不对,现在我们将build目录的文件(本工程中只有HTML和JS文件,不需要包含编译合约时生成的contracts子目录)拷贝到新的位置,并且启用任何Web服务器构建工具(最简单的方法是在新位置下执行python.exe -m SimpleHTTPServer,如果已经安装了Python的话):

没错,因为Ganache还在运行,私有链依然存在,所以当前用户(默认总是用户)所持有的MetaCoin代币还是9000个,尝试给另一个用户发送代币之后:

一切顺利!并且如果现在直接双击HTML文件在浏览器中打开的话,也可以看到正确的调用结果。这意味着我们未来有更多的方法去调用后端合约代码,例如通过libcef(即Chromium Embedded Framework,地址为https://bitbucket.org/chromiumembedded/cef)这样的嵌入式开发库,在C/C++或者Unity游戏引擎中直接调用这里的JS代码(当然这并不是一个很优雅的方案,不过对于初学者来说足够直接)。也许我们在后面的系列教程中会进行这样的尝试。

2、合约的发布脚本和流程

现在我们来关注一个之前被忽略的问题:“truffle.cmd migrate”这个命令到底做了什么?

顾名思义,migrate的过程也就是把已经编译完成的智能合约“移植”(或者更准确的说,就是发布)到区块链上的过程。这个过程同样需要通过相对简单的JavaScript来进行控制。

发布的过程可以被人为划分成多个阶段,每个阶段都通过一个单独的.js脚本来控制,而这些脚本都被保存在工程的migrations子目录中,在我们目前所使用的MetaCoin/WebPack例子中,该目录下都只有两个文件,分别是:

1_initial_migration.js:负责发布Migrations.sol中的智能合约,这个合约是Truffle自动生成的,有自己内置的接口标准,通常不需要用户进行改动。它主要被用来管理发布合约的所有历史数据。

2_deploy_contracts.js:负责发布用户编写的智能合约,本工程中它们被保存在MetaCoin.sol(合约代码)和ConvertLib.sol(辅助功能代码)两个文件中,并且后者被前者所引用。

这种类似“序号_说明.js”的文件命名方式并不是必需的,不过它起到了两个主要的作用:其一是可读性,用户很容易从文件名理解发布合约的每个阶段在做什么;其二是确立正确的执行顺序,Truffle在发布过程中会按照文件名排序来执行各个阶段的脚本,以数字序号作为开头无疑确保了执行的正确顺序。

此外,每次我们执行“truffle.cmd migrate”的时候,它都会自动判断用户代码是否发生了改动,以及上一次发布合约时的最后一个阶段在哪里。然后查找用户是否在migrations目录中添加了新的阶段脚本,并且仅运行新的脚本。因此,如果我们在控制台中反复输入这个指令(此过程中对合约代码和发布脚本不做任何改动),第二次会给出“up to date”(不需要任何改动)的提示。

这对于合约发布的过程是很有实际价值的,毕竟以太坊区块链上的所有“写入”操作都需要消耗对应的资源(ETH)。如果我们因为错误的操作反复发布完全相同的合约,那么自然就白白损失了自己的“以太”。Truffle的发布流程可以确保只有改动过或者新增的合约被发布到链上,避免了财产损失,也避免链上堆积大量无用的数据。

注意,我们可以通过下面的指令来强制重新发布所有的合约:

# truffle.cmd migrate --reset

对于Truffle而言,使用migrate发布合约就意味着付出ETH作为代价(并且在此之前区块链必须已经可用);不过compile是可以随时随意执行的,所有的操作都只作用于本地磁盘上。

打开文件2_deploy_contracts.js,里面的内容基本上是一目了然的程度:

var ConvertLib = artifacts.require("./ConvertLib.sol"); //获取ConvertLib接口对象

var MetaCoin = artifacts.require("./MetaCoin.sol"); //获取MetaCoin接口对象

module.exports = function(deployer) { //固定格式,定义合约发布函数

deployer.deploy(ConvertLib); //发布ConvertLib到区块链上

deployer.link(ConvertLib, MetaCoin); //链接ConvertLib到MetaCoin

deployer.deploy(MetaCoin); //发布MetaCoin到区块链上

};

通过前一篇文章的阅读我们已经知道,ConvertLib的作用是为MetaCoin提供辅助功能函数(虽然只有一个convert()),并且MetaCoin.sol的合约代码中也把它作为导入库使用。这很像是C/C++语言里使用#include来包含函数声明的做法;以此类推,这里我们使用deployer.link()将ConvertLib链接到MetaCoin的行为,也就可以类比成C/C++中的Link。这之后才能生成正确的合约结果并且发布。

而合约涉及的所有接口对象都是通过artifacts.require()来获取的,值得注意的是,因为本工程中每个.sol文件中只定义了一个接口,因此require()的参数可以直接传递contracts目录中的文件名。如果.sol中定义了多个接口,例如在ContractData.sol中定义了ContractA和ContractB两个接口对象,那么发布脚本中需要通过下面的语句来分别获取两个对象:

var ContractA = artifacts.require("ContractA");

var ContractB = artifacts.require("ContractB");

更多有关发布脚本和流程的内容,可以参考官方的入门文档:

http://truffleframework.com/docs/getting_started/migrations

3、通过网站发布DAPP程序

我们在前面的章节中已经修改过前端界面对应的app.js文件的内容,以便正确对应区块链的服务器地址和端口号,现在我们再次打开这个文件(对于WebPack工程,它位于app/javascripts/目录下),简单了解一下它都做了哪些工作。

(1)文件的开头部分首先导入必需的依赖库(web3和truffle-contract)以及编译后的合约文件,并建立唯一的抽象层接口MetaCoin。

(2)之后我们定义一个JS层面的App接口,它主要包括四个成员函数,分别是:

App.start():将区块链的web3接口设置给MetaCoin抽象层,同时从web3.eth获取用户列表,设置交易的发起用户(本工程中总是设置为accounts[0])并刷新他持有的代币数。

App.setStatus():设置HTML界面上的日志输出内容。

App.refreshBalance():刷新当前用户持有代币总数的数额和界面显示,这里通过MetaCoin的实例对象触发getBalance()方法,实现了JS前端对合约接口的调用。

App.sendCoin():向界面中给定的用户地址发送一定数额的代币,这里通过MetaCoin的实例对象触发sendCoin()方法,实现了JS前端对合约接口的调用(HTML文件中也有相应的代码)。

(3)在window.addEventListener('load')所包含的代码段中,我们从预先设置好的区块链服务器地址和端口尝试获取一个web3接口对象。因为这段代码是在网页加载时自动执行的,因此可以确保HTML页面加载完成后就会获取对象,然后执行App.start()初始化合约调用的相关接口。

事实上,Truffle甚至还提供了一个浏览器端的工具MetaMask,可以通过插件的方式直接加载到浏览器中,帮助编译、发布和审阅合约,连接区块链服务。这样的话也就不需要在app.js中设置区块链服务器的地址和端口了。这个工具的相关说明可以参看:

http://truffleframework.com/docs/advanced/truffle-with-metamask

而window.addEventListener('load')中也会优先尝试通过MetaMask直接获取web3对象,如果失败再通过JS文件中提供的地址连接。当然,之后的合约接口调用都是一致的。

现在,我们要尝试将WebPack这个工程发布到一个简易的网站上,让其他客户端(例如手机)也可以访问界面,交互性地完成智能合约的查询和交易操作。我们可以简单地在控制台中通过ipconfig来查询自己主机的IP地址(笔者为192.168.0.101)。

将这个IP地址分别填写到工程根目录的truffle.js文件(用于配置合约的发布脚本),app/javascripts/app.js文件(配置前端访问),以及Ganache的服务器设置页面中:

选择“保存并重新启动”(Save & Restart)之后,这里有一个需要注意的事情:Ganache本身是一个测试专用的私有链生成和维护工具,一旦它被关闭或者重启,链上的所有区块,以及区块上的合约也都会被清空重置。再加上我们修改了合约相关的多个脚本文件,因此这里最好重新执行一遍“编译-发布-生成前端页面”的指令流程为宜:

# truffle.cmd compile

# truffle.cmd migrate

# npm run build

之后我们可以进入build子目录,或者将新生成的index.html和app.js拷贝到新的目录,并建立一个最基础的服务器(例如通过Python):

# python.exe -m SimpleHTTPServer 8000

在浏览器端打开http://192.168.0.101:8000,看是否能正确显示代币总量,然后再给任意用户地址发送一些代币,看看交易能否顺利完成?

同时,我们也可以通过其它客户端(例如手机浏览器)进入到同一个网站,可以刷新观察代币的总量变化,或者执行更多交易操作了。

至此,虽然还很原始,但是我们已经拥有了正常运行的智能合约后端,基于HTML+JS的前端界面,以及通过多个客户端去访问它们了。下一步,该是自己创作一些内容的时候了。

欢迎随便转载。

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180217G05AD900?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券