目录
[5.启动 Webpack 开发服务器](#5.启动 Webpack 开发服务器)
在前一往篇文章基础上操作:
https://blog.csdn.net/fyihdg/article/details/155675039
https://blog.csdn.net/fyihdg/article/details/155675039 已经搭建好环境,写一个简单的前端代码,创建一个网页界面,让用户可以通过浏览器与区块链上的 Counter 合约交互,连接用户的MetaMask钱包 ,与一个已部署在以太坊测试网上的 Counter 智能合约交互,显示当前计数值,并提供一个按钮让用户调用 count() 函数来递增它,同时通过事件监听实时更新界面。
1.DApp前端代码
在vscode右键,新增 src目录,新建index.html,index.ts文件
index.html:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
index.ts
javascript
import { ethers } from "ethers";
import { abi } from '../artifacts/contracts/Counter.sol/Counter.json';
function getEth() {
// @ts-ignore
const eth = window.ethereum;
if (!eth) {
throw new Error("No ethereum provider found");
}
return eth;
}
async function requestAccess() {
const eth = getEth();
const result = await eth.request({ method: "eth_requestAccounts" }) as string[];
return result && result.length > 0;
}
async function hasSigners() {
const metamask = getEth();
const signers = await metamask.request({ method: "eth_accounts" }) as string[];
return signers.length > 0;
}
async function getContract() {
// 1. 地址
// 2. 方法名
// 3. provider
// 4. signer
if (!await hasSigners() && !await requestAccess()) {
throw new Error("No ethereum provider found");
}
const provider = new ethers.BrowserProvider(getEth());
const address = "0x5FbDB2315678afecb367f032d93F642f64180aa3";
const contract = new ethers.Contract(
address,
abi,
await provider.getSigner(),
)
const counter = document.createElement("div");
async function getCount() {
counter.innerHTML = await contract.getCount();
}
getCount();
const btn = document.createElement("button");
btn.innerHTML = "increment";
btn.onclick = async function () {
await contract.count();
}
contract.on(contract.filters.CounterInc(), async function ({ args }) {
counter.innerHTML = args[0].toString() || await contract.getCount();
})
document.body.appendChild(counter);
document.body.appendChild(btn);
}
async function main() {
await getContract();
}
main();
在package.json文件所在目录下新建,webpack.common.js,webpack.dev.js,webpack.prod.js,.env文件
webpack.common.js
javascript
const dotenv = require("dotenv");
dotenv.config();
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/index.ts", // bundle"s entry point
output: {
path: path.resolve(__dirname, "dist"), // output directory
filename: "[name].js", // name of the generated bundle
},
resolve: {
extensions: [".js", ".ts", ".json"],
},
module: {
rules: [
{
test: /\.ts$/,
loader: "ts-loader",
exclude: /node_modules/,
},
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
inject: "body",
}),
],
};
webpack.dev.js
javascript
const webpack = require("webpack");
const { merge } = require("webpack-merge");
const baseConfig = require("./webpack.common");
module.exports = merge(baseConfig,{
mode:"development",
plugins: [
new webpack.DefinePlugin({
'process.env.CONTRACT_ADDRESS': JSON.stringify(process.env.CONTRACT_ADDRESS),
'process.env.DEBUG': JSON.stringify(process.env.DEBUG),
}),
],
devServer: {
historyApiFallback: true,
port:8080,
hot:true
}
});
webpack.prod.js
javascript
const webpack = require("webpack");
const baseConfig = require("./webpack.common");
const { merge } = require("webpack-merge");
module.exports = merge(baseConfig,{
mode: "production",
plugins: [
new webpack.DefinePlugin({
'process.env.CONTRACT_ADDRESS': JSON.stringify(process.env.CONTRACT_ADDRESS),
'process.env.DEBUG': JSON.stringify(process.env.DEBUG),
}),
],
});
.env文件
javascript
# .env 这个文件不能提交到git中
PRIVATE_KEY=私钥地址
ALCHEMY_API_KEY=ALCHEMY的key
package.json添加配置
javascript
"scripts": {
"dev": "webpack serve --config webpack.dev.js",
"build": "webpack --config webpack.prod.js",
"deploy:local": "npx hardhat run scripts/deploy-counter.ts --network localhost"
}

2.solidity代码
在contracts目录下新建Counter.sol文件
javascript
pragma solidity ^0.8.24;
import "hardhat/console.sol";
contract Counter {
uint counter;
event CounterInc(uint counter);
function count() public {
counter++;
console.log("Now, counter is: ", counter);
emit CounterInc(counter);
}
function getCount() public view returns (uint) {
return counter;
}
}

3.编写部署代码
在package.json所有目录下新增scripts目录,创建deploy-counter.ts文件
javascript
import "@nomicfoundation/hardhat-ethers";
import { ethers } from "hardhat";
async function deploy() {
const Counter = await ethers.getContractFactory("Counter");
const counter = await Counter.deploy();
await counter.waitForDeployment();
console.log('counter address is', await counter.getAddress());
return counter;
}
async function count(counter: any) {
await counter.count();
console.log('count is',await counter.getCount());
}
deploy().then(count);
4.连接小狐狸钱包
创建一个钱包,打开谷歌浏览器,输入地址:https://metamask.io/zh-CN/download

我选择添加扩展

然后点击:

就可以找到狐狸钱包了

我们选创建一个钱包

要用 Google/Apple(因为没有备份可恢复),对于大多数加密货币用户:建议只用助记词方式,完全自主控制,不依赖任何第三方服务。Google/Apple 方式适合追求便捷的轻度用户,但要知道其中的信任依赖。


屏幕显示 12 个单词 → 立即手抄!,不要拍照,谁拿到你的助记词,谁就等于拥有了你的全部加密资产!



首先在根目录执行,启动一个 本地以太坊区块链节点(只在你电脑上运行),让前端 DApp 能通过 MetaMask 与本地合约交互,让你能在真实环境中测试 DApp,而无需花费真钱或等待测试网确认。记住这些只是测试工具,不要与真实钱包混淆!
- 自动生成 20 个测试账户 ,每个账户包含:
- 一个 私钥
- 一个 地址
- 10,000 个测试 ETH(仅限本地使用,没有真实价值)
javascript
npx hardhat node

注意这个地址: http://127.0.0.1:8545/,配置网络:


导出测试的私钥



把控制台打印的私钥复制进来
导入后:
- MetaMask 账户 = Hardhat 账户
- 有钱 + 有权限 → 开发调试畅通无阻!

5.启动 Webpack 开发服务器
首先部署合约
javascript
# 在另一个终端窗口部署合约
npx hardhat run scripts/deploy-counter.ts --network localhost
javascript
D:\csdn\Hardhat2.22.17>npx hardhat run scripts/deploy-counter.ts --network localhost
counter address is 0x5FbDB2315678afecb367f032d93F642f64180aa3
count is 1n
D:\csdn\Hardhat2.22.17>
启动 Webpack 开发服务器
javascript
npx webpack serve --config webpack.dev.js
javascript
D:\csdn\Hardhat2.22.17>npx webpack serve --config webpack.dev.js
[dotenv@17.2.3] injecting env (0) from .env -- tip: 🔐 encrypt with Dotenvx: https://dotenvx.com
<i> [webpack-dev-server] Project is running at:
<i> [webpack-dev-server] Loopback: http://localhost:8080/, http://[::1]:8080/
<i> [webpack-dev-server] On Your Network (IPv4): http://198.18.0.1:8080/
<i> [webpack-dev-server] On Your Network (IPv6): http://[fdfe:dcba:9876::1]:8080/
<i> [webpack-dev-server] Content not from webpack is served from 'D:\csdn\Hardhat2.22.17\public' directory
<i> [webpack-dev-server] 404s will fallback to '/index.html'
asset main.js 1.49 MiB [emitted] (name: main)
asset index.html 242 bytes [emitted]
runtime modules 27.9 KiB 12 modules
modules by path ./node_modules/ethers/lib.commonjs/ 889 KiB 118 modules
modules by path ./node_modules/ethers/node_modules/ 179 KiB 20 modules
modules by path ./node_modules/aes-js/lib.commonjs/*.js 66.8 KiB
./node_modules/aes-js/lib.commonjs/index.js 1.6 KiB [built] [code generated]
+ 8 modules
modules by path ./node_modules/webpack-dev-server/client/ 84.8 KiB
modules by path ./node_modules/webpack-dev-server/client/*.js 53.3 KiB 4 modules
modules by path ./node_modules/webpack-dev-server/client/utils/*.js 980 bytes 2 modules
+ 2 modules
modules by path ./node_modules/webpack/hot/*.js 5.17 KiB
./node_modules/webpack/hot/dev-server.js 1.94 KiB [built] [code generated]
./node_modules/webpack/hot/log.js 1.73 KiB [built] [code generated]
+ 2 modules
+ 5 modules
webpack 5.103.0 compiled successfully in 3441 ms
在浏览器中输入:http://localhost:8080

点击连接

点击'increment'按钮

点"确认"代表你在授权一个区块链交易!这是 MetaMask 交易确认弹窗
弹窗信息解读
✅ 网络:Hardhat Localhost(本地测试网)
✅ 请求来自:localhost:8080(你的前端DApp)
✅ 交互对象:合约地址(0x5FbDB...80aa3)
✅ 网络费:<US0.01(测试币,实际0)
const btn = document.createElement("button");
btn.innerHTML = "increment";
btn.onclick = async function () {
await contract.count(); // ⬅️ 这里触发的!
}
**// 点击确认后:
- ✅ 发送一个交易到 Counter 合约
- ✅ 调用 count() 函数
- ✅ 合约中的计数器 +1
- ✅ 触发 CounterInc 事件
- ✅ 前端监听到事件,更新显示**
区块链层面的操作:
// Counter.sol 合约中的函数
function count() public {
count += 1; // 状态变量+1
emit CounterInc(count); // 发出事件
}
**// 这个操作会:
- 🔄 修改区块链状态(count值改变)
- 📝 产生一个交易记录
- ⛓️ 被添加到区块中
- 🔥 消耗少量 Gas(测试币)**

至此,前端可以调用到合约了
6.部署到Alchemy测试网
打开网址:https://dashboard.alchemy.com/apps/vatwoz0eoicb1mih/setup

需要注意的是,最好弄一个gmail邮箱,qq邮箱容易被拦截。注册成功后

只选这个就行

下一步




去小狐狸复制私钥,选择这三个小点都可以:






.env文件,把私钥填写到:
从Endpoint URL复制:https://eth-sepolia.g.alchemy.com/v2/nr6k9FBd4Rvwi83XSr6xR
ALCHEMY_API_KEY就是:nr6k9FBd4Rvwi83XSr6xR

PRIVATE_KEY=填写刚刚复制的私钥,添加前辍0x
ALCHEMY_API_KEY=nr6k9FBd4Rvwi83XSr6xR
编辑hardhat.config.ts文件:
javascript
import "hardhat-gas-reporter"
import "@nomicfoundation/hardhat-toolbox";
import "@nomicfoundation/hardhat-ethers";
import "@nomicfoundation/hardhat-verify";
const dotenv = require('dotenv');
dotenv.config();
type Config = import('hardhat/config').HardhatUserConfig
const config: Config = {
solidity: "0.8.28",
networks: {
hardhat: {
chainId: 31337
},
sepolia_eth: {
url: `https://eth-sepolia.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}`,
accounts: [process.env.PRIVATE_KEY]
}
// 如果没有配置环境变量,这里先注释掉执行
// sepolia_eth: {
// url: `https://eth-sepolia.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}`,
// accounts: [process.env.PRIVATE_KEY]
// }
},
gasReporter: {
enabled: true
}
};
export default config;
tsconfig.json文件,把这个"strict": true,注释掉
javascript
{
"compilerOptions": {
"target": "es2020",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
//"strict": true,
"skipLibCheck": true,
"resolveJsonModule": true
}
}
运行命令:
javascript
npx hardhat run scripts/deploy-counter.ts --network sepolia_eth
成功了
javascript
D:\csdn\Hardhat2.22.17>npx hardhat run scripts/deploy-counter.ts --network sepolia_eth
[dotenv@17.2.3] injecting env (2) from .env -- tip: 🔄 add secrets lifecycle management: https://dotenvx.com/ops
[dotenv@17.2.3] injecting env (0) from .env -- tip: 🔑 add access controls to secrets: https://dotenvx.com/ops
counter address is 0x920F394f09E6B17133e94c7AF0a86dF5A5cB357B
count is 0n
D:\csdn\Hardhat2.22.17>



成功上测试网了,注意,不是主网