背景
本文将学习谷歌插件的钱包开发。实现最基本的钱包插件开发。
准备工作
我们需要开发一款运行在浏览器插件的钱包,我们的开发环境是插件环境,所以我们必须使用一个完善的类似于create-react-app
,来为我们生成代码仓库。
这里我们使用的是Plasmo,借用 Plasmo 文档的话,这是 “一个一体化平台,可让浏览器扩展程序开发人员轻松创建、测试和发布出色的扩展程序。借助 Plasmo,您可以告别繁琐的样板代码,享受更快、更无缝的开发体验。”
开发文档地址:docs.plasmo.com/
github 仓库地址:github.com/PlasmoHQ/pl…
目前已经有 8.7k star属于一个比较成熟的框架。
所以选用这个作为我们的插件模板。
生成代码模板
使用
pnpm create plasmo
# OR
yarn create plasmo
# OR
npm create plasmo
生成如下代码:
这里着重说一下popup.tsx
此文件导出默认的 React 组件,该组件将呈现到您的弹出页面中。这就是您在扩展弹出窗口上工作所需的全部内容!
安装其他组件
Web3 交互需要使用到的 ethers.js
其他的工具方法:
- react-loadable 组件懒加载
- react-loadable 组件懒加载
- queue 队列,用于插件通信
- eth-rpc-errors rpc 报错
- @extend-chrome/storage 插件存储
- @metamask/browser-passworder 插件密码管理
- ahooks 公共 hooks
- classnames
至此我们的插件模板就安装完成!!
下面进入到代码层面的开发阶段
路由配置
这里添加我们页面的路由,比如创建钱包页面、资产页面、设置页面等、以及密码页
我们需要加入ErrorBoundary
,这个方法用于监听页面错误。
export class ErrorBoundary extends React.Component {
state = { hasError: false }
static getDerivedStateFromError(error: any) {
return { hasError: true }
}
componentDidCatch(error: any, errorInfo: any) {
console.error(error, errorInfo)
}
render() {
if (this.state.hasError) {
return (
<Result
status="error"
title="系统异常"
subTitle="系统异常,请稍后重试"
extra={[
<Button
type="primary"
onClick={() => {
message.success("上报成功")
}}>
上报错误
</Button>,
<Button
key="buy"
onClick={() => {
window.location.reload()
}}>
刷新重试
</Button>
]}></Result>
)
}
// @ts-ignore
return this.props.children
}
}
接下来需要配置一下我们的状态管理。插件钱包需要很多的状态管理,比如账号状态、钱包状态、当前链等等。
这里我们使用unstated-next
作为我们的状态管理。
在根目录下创建 stroe
文件夹,预先创建 2 个文件 ChainStore.ts
WalletStore.ts
代码目录结构如下:
index.js
import { ChainStore } from "./ChainStore";
import { WalletStore } from "./WalletStore";
export const walletProviders = [WalletStore.Provider, ChainStore.Provider];
ChainStore.ts
/**
* 链的数据存储
*/
import { createContainer } from 'unstated-next';
const useChainStore = () => {};
export const ChainStore = createContainer(useChainStore);
Nested
组件,这个组件的功能是可以接受多个 Providers,代码如下:
import React, { memo, type PropsWithChildren } from "react"
interface Props {
components: Array<React.ComponentType | (({ children }) => JSX.Element)>
}
export const Nested = memo(function NestedComponents(
props: PropsWithChildren<Props>
) {
const { components, children } = props
return (
<>
{components.reduceRight((Prev, Curr) => {
return <Curr>{Prev}</Curr>
}, children)}
</>
)
})
状态管理的基本代码书写完毕,接下来进行路由的配置:
我们使用的是react-loadable
react 组件懒加载,用于性能优化,再加载页面的过程中可以放入骨架屏等,减少用户等待时间等。所以这个在加载页面组件时,用懒加载进行处理,例如插件钱包一开始的进入的页面。
import Loadable from "react-loadable"
import React from "react";
import { PageLoading } from "~components/PageLoading";
/* 页面初始化 */
const InitPage = Loadable({
loader: () => import("~pages/InitPage"),
loading: PageLoading
})
import { Skeleton } from "antd-mobile";
import React from "react"
import { memo } from "react"
export const PageLoading = memo(() => {
return (
<>
<Skeleton.Title animated />
<Skeleton.Paragraph lineCount={5} animated />
</>
)
})
接下来需要写一个组件,用于路由跳转的逻辑,比如一开始用户没有创建钱包,我们需要引导去创建钱包页面。所以这里需要添加一个组件
export const RenderRoutes = memo(() => {
const nav = useNavigate()
useRequest(() => wallet.getAllWallets(), {
onSuccess: (data) => {
if (data?.length > 0) {
nav("/home")
}
}
})
const Route = useRoutes(routes)
return <div className="w-full h-full max-w-[360px] mx-auto">{Route}</div>
})
关于wallet.getAllWallets()
这些跟钱包交互相关的,这里就先埋个坑,等后续更新钱包交互,再来书写这块
组装我们的组件。
export const Root = memo(() => {
return (
<Nested components={walletProviders}>
<HashRouter>
<ErrorBoundary>
<RenderRoutes />
</ErrorBoundary>
</HashRouter>
</Nested>
)
})
最后在 popup.tsx 替换为:
import React from "react";
import { Root } from "~routes";
export default function Popup() {
return <Root />
}
运行pnpm dev
,将 build 下的 chrome-mv3-dev
文件夹,放入chrome://extensions/
第一章完毕!!