スタイルシートの追加

カスケードスタイルシートは、フロントエンドのユーザーインターフェースの重要な部分です。デフォルトのスターターでは、編集可能な main.css ファイルをインポートするように設定されていますが、スタイルシートを JavaScript ファイルにインポートしたり、 Syntactically Awesome Style Sheets (SCSS) のような代替フォーマットを使用したりすることもできます。 このチュートリアルでは、"Contacts" Dapp の構築を通じて、スタイルシートをインポートするように webpack を設定する方法を説明します。 webpack ベースのプロジェクトにカスケーディング・スタイルシート(CSS)を追加する方法をすでにご存知の方は、このチュートリアルを読み飛ばしていただいて構いません。

このチュートリアルでは、 React フレームワークを使ってフロントエンドの DOM(Document Object Model)を管理する方法を説明します。 React には独自のカスタム DOM 構文があるため、 JSX で書かれたフロントエンドコードをコンパイルするためには、 webpack の設定を変更する必要があります。React と JSX の使い方の学習については、 ReactのウェブサイトGetting started を参照してください。

始める前に

チュートリアルを始める前に、以下のことを確認してください。

  • フロントエンド開発のために node.js がインストールされており、プロジェクトで npm install を使用してパッケージをインストールできること。 ローカルのオペレーティングシステムやパッケージマネージャに node をインストールする方法については、 Node のウェブサイトを参照してください。

  • ダウンロードとインストール で説明されている通り、 DFINITY Canister SDK パッケージをダウンロードしてインストールしていること。

  • IDE として Visual Studio Code を使用している場合、 language editor plug-inのインストール で説明されているように、 Motoko の Visual Studio Code プラグインをインストールしていること。

  • ローカルコンピュータ上で実行されているローカルキャニスターの実行環境プロセスを停止していること。

このチュートリアルでは、 DFINITY Canister SDK のバージョン 0.8.0 以降を使用する必要があります。

新しいプロジェクトを生成

カスタムフロントエンド Dapp 用の新しいプロジェクトディレクトリを作成するには:

  1. ローカルコンピューターでターミナルシェルを開きます(まだ開いていない場合)。

  2. Internet Computer プロジェクトで使用しているフォルダがあれば、そのフォルダに変更します。

  3. 必要に応じて、ローカルに node.js がインストールされていることを確認します。

  4. 次のコマンドを実行して、新しいプロジェクトを作成します:

    dfx new contacts
  5. 以下のコマンドを実行して、プロジェクト・ディレクトリに変更します:

    cd contacts

React フレームワークのインストール

React を使ったことがない方は、フロントエンドのコードを編集する前に、 React 紹介 チュートリアルや、 React ウェブサイト をご覧になるとよいでしょう。

必要なフレームワークモジュールをインストールするには:

  1. 以下のコマンドを実行して、Reactモジュールをインストールします:

    npm install --save react react-dom
  2. 以下のコマンドを実行して、必要な TypeScript 言語のコンパイラ・ローダをインストールします:

    npm install --save-dev typescript ts-loader
  3. 以下のコマンドを実行して、必要なスタイルローダーをインストールします:

    npm install --save-dev style-loader css-loader

    また、 npm install コマンドで脆弱性が報告された場合は、 npm audit fix コマンドを実行して、報告された脆弱性の修正を試みてから続行するとよいでしょう。

    これらのモジュールをインストールする代わりに、デフォルトの package.json ファイルを編集して、プロジェクトの依存関係を追加することができます。
    {
      "name": "contacts_assets",
      "version": "0.1.0",
      "description": "",
      "keywords": [],
      "scripts": {
        "build": "webpack"
      },
      "devDependencies": {
        "assert": "2.0.0",
        "buffer": "6.0.3",
        "css-loader": "^5.2.1",
        "events": "3.3.0",
        "html-webpack-plugin": "5.3.1",
        "process": "0.11.10",
        "stream-browserify": "3.0.0",
        "style-loader": "^2.0.0",
        "terser-webpack-plugin": "5.1.1",
        "ts-loader": "^8.1.0",
        "typescript": "^4.2.4",
        "util": "0.12.3",
        "webpack-cli": "4.5.0",
        "webpack": "5.24.4"
      },
      "dependencies": {
        "@dfinity/agent": "0.10.0",
        "@dfinity/candid": "0.10.0",
        "@dfinity/principal": "0.10.0",
        "react-dom": "^17.0.2",
        "react": "^17.0.2"
      }
    }

    この例の package.json ファイルに含まれている JavaScript エージェントのバージョンは 0.10.0 です。しかし、ほとんどの場合、利用可能な最新バージョンのエージェントを使用することをお勧めします。新しいプロジェクトを作成すると、 dfx new コマンドが自動的に最新バージョンの JavaScript エージェントを取得します。また、プロジェクトを作成した後に npm install --save @dfinity/agent コマンドを実行して、手動で最新バージョンを取得することもできます。

デフォルトプログラムの変更

このチュートリアルでは、メインプログラムを修正して、連絡先情報を保存したり検索したりするコードを追加します。

デフォルトプログラムを変更するには:

  1. テキストエディタで src/contacts/main.mo ファイルを開き、既存のコンテンツを削除します。

  2. 以下のサンプルコードをコピーして、ファイルに貼り付けます:

    import List "mo:base/List";
    import AssocList "mo:base/AssocList";
    
    actor Contact {
    
      var contacts : ContactsMap = List.nil();
    
      type Name = Text;
      type Phone = Nat;
    
      type Entry = {
        name : Name;
        address1 : Text;
        address2 : Text;
        email : Text;
        phone : Phone;
      };
    
      type ContactsMap = AssocList.AssocList<Name, Entry>;
    
      func nameEq(lhs : Name, rhs : Name) : Bool {
        return lhs == rhs;
      };
    
      public func insert(name : Name, address1 : Text, address2 : Text, email : Text, phone : Phone) : async () {
         let newEntry : Entry = {
           name;
           address1;
           address2;
           email;
           phone;
         };
    
         let (newContacts, _) = AssocList.replace(
           contacts,
           name,
           func(n: Name, m: Name) : Bool { n == m },
           ?newEntry
         );
         contacts := newContacts;
      };
    
      public query func lookup(name : Name) : async ?Entry {
        return AssocList.find(contacts, name, nameEq);
      };
    };
  3. 変更を保存し、 main.mo ファイルを閉じて次に進みます。

フロントエンドファイルの変更

これで、プログラムの新しいフロントエンドを作成する準備が整いました。

  1. テキストエディターで、 webpack の設定ファイル( webpack.config.js )を開きます。

  2. フロントエンドのエントリーを修正し、デフォルトの index.html を index.jsx に置き換えます。

    entry: {
      // フロントエンド・エンドポイントは、このビルドのHTMLファイルを指しているので
      // 拡張子を `.js` に変更する必要があります。
      index: path.join(__dirname, asset_entry).replace(/\.html$/, ".jsx"),
    },
  3. コメントされた module キーの例を plugins セクションの上に見つけて、次の行をアンコメントしてください。

    module: {
      rules: [
        { test: /\.(js|ts)x?$/, loader: "ts-loader" },
        { test: /\.css$/, use: ['style-loader','css-loader'] }
      ]
    },
  4. これらの設定により、プログラムが ts-loader コンパイラを使用したり、CSS ファイルをインポートしたりすることができます。

    Note: .scss.sass ファイルのサポートを追加したい場合は、 sass-loader を一緒にインストールしてください。

    npm install --save react react-dom

    それから、 webpack.config.jscss-loader ルールの下にこの追加ルールを追加します。

    module: {
      rules: [
        // ...
        {
          test: /\.s[ac]ss$/i,
          use: [
            // `style` ノードをJS文字列から生成
            "style-loader",
            // CSS を CommonJS に翻訳する
            "css-loader",
            // Sass を CSS にコンパイルする
            "sass-loader",
          ],
        },
      ]
    },
  5. 変更内容を保存し、 webpack.config.js ファイルを閉じて次に進みます。

  6. プロジェクトのルートディレクトリに、 tsconfig.json という名前の新規ファイルを作成します。

  7. テキストエディターで tsconfig.json ファイルを開き、以下をコピーしてファイルに貼り付けます:

    {
        "compilerOptions": {
          "target": "es2018",        /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
          "lib": ["ES2018", "DOM"],  /* Specify library files to be included in the compilation. */
          "allowJs": true,           /* Allow javascript files to be compiled. */
          "jsx": "react",            /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
        },
        "include": ["src/**/*"],
    }
  8. 変更内容を保存し、 tsconfig.json ファイルを閉じて次に進みます。

スタイルシートをプロジェクトに追加

これで、新しいカスケードスタイルシートを作成し、プロジェクトに追加する準備が整いました。

スタイルシートをプロジェクトに追加するには:

  1. src/contacts_assets/assets ディレクトリに移動します。

    cd src/contacts_assets/assets/
  2. テキストエディターで main.css ファイルを開き、既存の内容を削除します。

  3. フロントエンド用にいくつかのスタイルプロパティを定義します。

    例えば、以下のサンプルスタイルをコピーして、ファイルに貼り付けます:

    html {
        background-color: bisque;
    }
    
    body {
        font-family: Arial, Helvetica, sans-serif;
        display: block;
        margin: 10px;
    }
    
    h1 {
        color: darkblue;
        font-size: 32px;
    }
    
    div.new-entry {
        margin: 30px 20px 30px 20px;
    }
    
    .new-entry > div {
        margin-bottom: 15px;
    }
    
    table {
        margin-top: 12px;
        border-top: 1px solid darkblue;
        border-bottom: 1px solid darkblue;
    }
    
    #form {
        margin: 30px 0 30px 20px;
    }
    
    button {
        line-height: 20px;
    }
    
    #lookupName {
        margin-right: 12px;
    }
  4. 変更内容を保存し、 main.css ファイルを閉じて次に進みます。

  5. src/contacts_assets/src ディレクトリに移動します。

    cd ../src
  6. デフォルトの index.js ファイルをテキストエディターで開き、既存のコンテンツを削除します。

  7. 以下のサンプルコードをコピーして、 index.js ファイルに貼り付けてください:

    import * as React from "react";
    import { render } from "react-dom";
    import { contacts } from "../../declarations/contacts";
    import "../assets/mycontacts.css";
    
    const Contact = () => {
      async function doInsert() {
        let name = document.getElementById("newEntryName").value;
        let add1 = document.getElementById("newEntryAddress1").value;
        let add2 = document.getElementById("newEntryAddress2").value;
        let email = document.getElementById("newEntryEmail").value;
        let phone = document.getElementById("newEntryPhone").value;
        contacts.insert(name, add1, add2, email, parseInt(phone, 10));
      }
    
      async function lookup() {
        let name = document.getElementById("lookupName").value;
        contacts.lookup(name).then((opt_entry) => {
          let entry;
    
          if (opt_entry.length == 0) {
            entry = { name: "", description: "", phone: "" };
          } else {
            entry = opt_entry[0];
          }
    
          document.getElementById("newEntryName").value = entry.name;
          document.getElementById("newEntryAddress1").value = entry.address1;
          document.getElementById("newEntryAddress2").value = entry.address2;
          document.getElementById("newEntryEmail").value = entry.email;
          document.getElementById("newEntryPhone").value = entry.phone.toString();
        });
      }
    
      return (
        <div className="new-entry">
          <h1>My Contacts</h1>
          <div>
            Add or update contact information:
            <form id="contact">
              <table>
                <tbody>
                  <tr>
                    <td>Name:</td>
                    <td>
                      <input id="newEntryName"></input>
                    </td>
                  </tr>
                  <tr>
                    <td>Address 1 (street):</td>
                    <td>
                      <input id="newEntryAddress1"></input>
                    </td>
                  </tr>
                  <tr>
                    <td>Address 2 (city and state):</td>
                    <td>
                      <input id="newEntryAddress2"></input>
                    </td>
                  </tr>
                  <tr>
                    <td>Email:</td>
                    <td>
                      <input id="newEntryEmail"></input>
                    </td>
                  </tr>
                  <tr>
                    <td>Phone:</td>
                    <td>
                      <input id="newEntryPhone" type="number"></input>
                    </td>
                  </tr>
                </tbody>
              </table>
            </form>
          </div>
          <div>
            <button onClick={() => doInsert()}>Add Contact</button>
          </div>
          <div>
            Lookup name:{" "}
            <input id="lookupName" style={{ lineHeight: "20px" }}></input>
            <button onClick={() => lookup()}>Lookup</button>
          </div>
        </div>
      );
    };
    
    document.title = "DFINITY CONTACT EXAMPLE";
    
    render(<Contact />, document.getElementById("contacts"));
  8. 以下のコマンドを実行して、修正した index.js ファイルの名前を index.jsx に変更します:

    mv index.js index.jsx
  9. デフォルトの src/contacts_assets/src/index.html ファイルをテキストエディターで開き、 main.css のリンクを削除して、 body の内容を <div id="contacts"></div> で更新します。

    例えば:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width" />
        <title>contacts</title>
        <base href="/" />
      </head>
      <body>
        <main>
          <div id="contacts"></div>
        </main>
      </body>
    </html>
  10. プロジェクトディレクトリのルートに戻ってください。

    例えば:

    cd ../../..

ローカルネットワークをスタート

contacts プロジェクトをビルドする前に、ローカルの Canister 実行環境に接続する必要があります。 ローカル実行環境をスタートするには:

  1. ローカルコンピュータで新しいターミナルウィンドウまたはタブを開きます。

  2. 次のコマンドを実行して、ローカルコンピュータ上でローカルキャニスター実行環境を起動します:

    dfx start --background

    環境の起動操作が完了したら、次のステップに進みます。

Dapp の登録、ビルド、デプロイ

開発環境のローカル Canister 実行環境に接続した後、テスト用の Dapp を登録、ビルド、デプロイすることができます。

Dapp をデプロイするには:

  1. 必要に応じて、プロジェクトのルートディレクトリにいることを確認します。

  2. 以下のコマンドを実行して、 Dapp の登録、ビルド、デプロイを行います:

    dfx deploy

    dfx deploy コマンドの出力には、実行した操作に関する情報が表示されます。

    なお、Canister の実行環境はローカルで動作しているため、 dfx deploy コマンドを実行したときに表示される識別子は、自分のマシンでのみ有効であることに注意してください。

    Internet Computer プラットフォーム にキャニスターをデプロイするには、 --network コマンドラインオプションを使用して、ローカル環境ではなく Internet Computer にデプロイすることを指定する必要があります。

    dfx deploy --network=ic
  3. Webpack development server を起動する:

    npm start

フロントエンドを確認する

これで、 contacts Dapp のフロントエンドにアクセス出来ます。

フロントエンドを確認するには:

  1. ブラウザを開き、 http://localhost:8080 に移動します。

  2. My Contacts フォームが表示されていることを確認します。

    例えば:

    Sample front-end

  3. "Name" 、"Address" 、"Mail" の各入力欄にテキストを入力し、 "Phone" の入力欄に数字を入力して"Add Contact" をクリックして、1つまたは複数のテストレコードを作成します。

  4. フォームフィールドをクリアして、 "Lookup name" フィールドに連絡先の名前を入力し、 Lookup をクリックすると、保存されている連絡先情報が表示されます。

    なお、入力する "Lookup" は、追加した連絡先の名前と完全に一致している必要があります。

スタイルシートを変更し、変更内容をテストする

Contacts Dapp を見た後、いくつかの変更を加えたいと思うかもしれません

スタイルシートのプロパティを変更するには:

  1. テキストエディターで src/contacts_assets/assets/mycontacts.css ファイルを開き、そのスタイル設定を修正します。

    例えば、背景色を変更したり、入力フォームのスタイルを変更したりすることができます。

    開いているブラウザウィンドウに変更がすぐに反映されるはずです。

フロントエンドやバックエンドのコードを変更する

さらに詳しく知りたい場合は、このチュートリアルのフロントエンドやバックエンドのコードを変更してみるとよいでしょう。 例えば、以下のように変更してみてはいかがでしょうか。

  • 新しい連絡先を追加した後に入力フィールドをクリアするように、フロントエンドのコードを変更しましょう。例えば、 onClick イベントの一部として。

  • プログラムの Motoko 関数を変更して、 Name フィールドで正確な文字列マッチングではなく、部分的な文字列マッチングを行うようにしましょう。(変更内容をローカル環境でテストするには、 dfx deploy を実行する必要があります)

  • 異なるフィールドに基づいて検索できるように Motoko プログラムを変更しましょう。

Canister ローカル実行環境の停止

プログラムの実験が終わったら、ローカル環境を停止して、バックグラウンドで実行し続けないようにすることができます。

Canister ローカル実行環境の停止するには:

  1. webpack の開発サーバーが表示されているターミナルで、 Control-C を押して開発サーバーを中断します。

  2. 以下のコマンドを実行して Internet Computer ネットワークを停止します。

    dfx stop