はじめに
以前、symbol-sdk@3を使用してWebアプリを作ろうとした際に、Next.jsならうまくいくことがわかりました。このアプリでは、Webpackを使っているため、他のWebpackを使っているアプリならうまく動かせるんじゃないかと思いました。
そこで今回は、Webpackを使用しているNuxt.jsのv2でやってみようと思います。
手順
それではプロジェクトを作っていきます。プロジェクト名はnuxt2
です。
$ npm init nuxt-app nuxt2
いろいろオプションを選択します。今回は以下のようにしました。
create-nuxt-app v5.0.0
✨ Generating Nuxt.js project in nuxt2
? Project name: nuxt2
? Programming language: JavaScript
? Package manager: Npm
? UI framework: None
? Template engine: HTML
? Nuxt.js modules:
? Linting tools:
? Testing framework: None
? Rendering mode: Single Page App
? Deployment target: Static (Static/Jamstack hosting)
? Development tools:
? What is your GitHub username? planethouki
? Version control system: None
| Installing packages with npm
必要パッケージをインストールします。
$ npm i symbol-sdk@3 symbol-crypto-wasm-web axios
この時点で、package.json
のdependenciesは以下のようになりました。
{
"dependencies": {
"axios": "^1.6.2",
"core-js": "^3.25.3",
"nuxt": "^2.15.8",
"symbol-crypto-wasm-web": "^0.1.1",
"symbol-sdk": "^3.1.0",
"vue": "^2.7.10",
"vue-server-renderer": "^2.7.10",
"vue-template-compiler": "^2.7.10"
}
}
nuxt.config.js
を以下のように編集します。
import webpack from 'webpack'
export default {
build: {
extend(config, ctx) {
config.module.rules.push({
test: /node_modules[\\/]symbol-sdk[\\/].*\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
});
},
plugins: [
new webpack.NormalModuleReplacementPlugin(
/symbol-crypto-wasm-node/,
'../../../symbol-crypto-wasm-web/symbol_crypto_wasm.js'
)
]
}
}
ページを作成します。
<template>
<div>
<div>
<span>秘密鍵:</span>
<input name="privateKey" v-model="privateKey">
</div>
<div>
<span>ノード</span>
<input name="nodeUrl" v-model="nodeUrl">
</div>
<hr />
<div>
<button @click="handleSend">トランザクションを送信</button>
</div>
<div>送信メッセ―ジ:{{ sendMessage }}</div>
<div>トランザクションハッシュ:{{ hash }}</div>
<hr />
<div>
<button @click="handleStatus">トランザクションを確認</button>
</div>
<div>トランザクションステータス:{{ statusMessage }}</div>
</div>
</template>
<script>
import symbolSdk from 'symbol-sdk'
import axios from 'axios'
export default {
name: 'IndexPage',
data() {
return {
privateKey: '',
nodeUrl: '',
sendMessage: '',
statusMessage: '',
hash: ''
}
},
methods: {
async handleSend() {
this.sendMessage = '送信中…'
this.hash = ''
const network = symbolSdk.symbol.Network.TESTNET
const facade = new symbolSdk.facade.SymbolFacade(network.name)
const privateKey = new symbolSdk.PrivateKey(this.privateKey)
const keyPair = new facade.constructor.KeyPair(privateKey)
const textEncoder = new TextEncoder()
const deadline = network.fromDatetime(new Date(Date.now() + 7200000)).timestamp
const transaction = facade.transactionFactory.create({
type: 'transfer_transaction_v1',
signerPublicKey: keyPair.publicKey.toString(),
fee: 1000000n,
deadline,
recipientAddress: 'TARDV42KTAIZEF64EQT4NXT7K55DHWBEFIXVJQY',
mosaics: [
{ mosaicId: 0x72C0212E67A08BCEn, amount: 1000000n },
],
message: new Uint8Array([0x00, ...textEncoder.encode('Hello, World!')])
})
const signature = facade.signTransaction(keyPair, transaction)
const jsonPayload = facade.transactionFactory.constructor.attachSignature(transaction, signature)
const hash = facade.hashTransaction(transaction).toString()
const sendResult = await axios.put(`${this.nodeUrl}/transactions`, JSON.parse(jsonPayload)).then((res) => res.data);
this.sendMessage = JSON.stringify(sendResult)
this.hash = hash
},
async handleStatus() {
this.statusMessage = '取得中…'
const statusResult = await axios
.get(`${this.nodeUrl}/transactionStatus/${this.hash}`)
.then((res) => res.data)
.catch((e) => e.message)
this.statusMessage = JSON.stringify(statusResult)
}
}
}
</script>
では、起動してみましょう。
$ npm run dev
見た目はこのようになりました。
それではトランザクションを送信して、確認してみると、unconfirmedになりました。
しばらくして再度確認すると、無事confirmedになりました。
苦労の足跡
上記の方法にたどり着くまでに、いろんなエラーにぶちあたりました。そのたびに、たくさんググったりChatGPTに聞いたりしました。その過程も何かの役に立つと思うので載せておきます。
プロジェクト作成
まずはプロジェクトを作成します。
$ npm init nuxt-app hoge
$ cd hoge
$ npm run dev
nuxt-app
のオプションについては上記で書いていたのと同じです。
次に、とりあえずsymbol-sdk@3だけインストールします。
$ npm i symbol-sdk@3
で、nuxt.config.js
にはいったん手を付けずに、ページを作成します。
トランザクションを送信するでもなく、秘密鍵から公開鍵を導出するだけの小さなページです。
<template>
<div>
{{ publicKey }}
</div>
</template>
<script>
import symbolSdk from 'symbol-sdk'
export default {
name: 'IndexPage',
data() {
const PRIVATE_KEY = '45461A042F439DAEE72BA235D953EA8457D1C9C11CBBE35E895882BEF44A72E9'
const network = symbolSdk.symbol.Network.TESTNET
const facade = new symbolSdk.facade.SymbolFacade(network.name)
const privateKey = new symbolSdk.PrivateKey(PRIVATE_KEY)
const keyPair = new facade.constructor.KeyPair(privateKey)
return {
publicKey: keyPair.publicKey.toString(),
}
}
}
</script>
すると画面はこのようになります。
目を覆ってしまいたくなりますが、ひとつひとつ見ていきます。
symbol-crypto-wasm-nodeエラー
まずは一番最初。
ERROR in ./node_modules/symbol-crypto-wasm-node/symbol_crypto_wasm.js
Module not found: Error: Can't resolve 'fs' in '\node_modules\symbol-crypto-wasm-node'
symbol-crypto-wasm-node
というワードは見たことがありますね。これです。
ですので、webpackのpluginの設定をnuxt.config.js
に書いていけばいいことになります。どのように書くかがわからないので、ドキュメントを探します。以下のようなものが見つかりました。
これを見習って、次のように書きます。
import webpack from 'webpack'
export default {
build: {
plugins: [
new webpack.NormalModuleReplacementPlugin(
/symbol-crypto-wasm-node/,
'../../../symbol-crypto-wasm-web/symbol_crypto_wasm.js'
)
]
}
}
そして次をインストールします。
$ npm i symbol-crypto-wasm-web
すると、画面は次のようになりました。
ひとつエラーが消えました。
Module parse failed: Identifier directly after number エラー
次のエラーに移りましょう。
ERROR in ./node_modules/symbol-sdk/src/BaseValue.js 10:37
Module parse failed: Identifier directly after number (10:37)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| throw new TypeError(`"value" (${value}) has invalid type, expected BigInt`);
|
> lowerBound = isSigned ? -0x80000000_00000000n : 0n;
| upperBound = isSigned ? 0x7FFFFFFF_FFFFFFFFn : 0xFFFFFFFF_FFFFFFFFn;
| } else {
Identifier directly after number
、つまり、数字の後ろに何らかの識別子が直接入っていることが問題のようです。
調べた過程はもう忘れてしまいましたが、0x80000000_00000000n
のように、数字の間に_
を入れることがダメである、というメッセージでした。
じゃぁ例えば、こういうものを用意したらどうなるのか。
<template>
<div>
{{ counter }}
</div>
</template>
<script>
export default {
name: 'IndexPage',
data() {
return {
counter: 0x345_388 + 0x453254
}
}
}
</script>
これは問題なく、エラー無く表示されます。
ということは、node_modules
の中にあるコードだからダメ(_
を数字の間に入れるような書き方はダメ)ということになります。
ChatGPTに、この書き方について聞いてみました。
推察すると、vueファイルのようなwebpackの処理が行われるファイルでは、数字の間に_
を入れていいが、node_modules
内のファイルでは、webpackの処理が何かしらスキップされるので、エラーとなってしまうのではないかと。(たぶん正確性に欠ける表現とは思いますが。)
というわけで、何とかnode_modules
内のファイルに対してwebpackの処理ができないかを探していました。
いろいろChatGPTに聞いたりした結果、以下のような回答が参考になりました。
ChatGPTに教えてもらった以下のwebpackの設定を見て、これを流用すれば、/node_modules/symbol-sdk以下のファイルに対してwebpackの処理がなされるのでは?
module: {
rules: [
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@bable/preset-env'],
}
}
]
}
これをnuxt.config.js
で書くために、以下のドキュメントを参考にしました。
そして、いろいろ試してみて、最終的にこのような形式にたどり着いた感じです。
export default {
build: {
extend(config, ctx) {
config.module.rules.push({
test: /node_modules[\\/]symbol-sdk[\\/].*\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
});
}
}
}
これを導入すると、プロジェクト作成のところで書いたコードが、うまく動くようになりました。
他にものこのようなエラーがあったのですが、それもなくなりました。(static修飾子が解釈されない)
ERROR in ./node_modules/symbol-sdk/src/symbol/models.js 11:13
Module parse failed: Unexpected token (11:13)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
|
| export class Amount extends BaseValue {
> static SIZE = 8;
|
| constructor(amount = 0n) {
static修飾子はそんなに古い表現ではなさそうだけど、webpackで何かしら処理してやらないとダメなんですね。
おわりに
Nuxt.jsのv2で使えることがわかりました。ただ、これがキレイな方法かどうかはわからないので、ほかに情報などがあれば、そちらも試してみるのもいいかもしれない。