Доступное пошаговое руководство для разработчиков и инженеров блокчейнов, которые привыкли размещать статические ресурсы в корзинах Netlify, Fastly или S3.

Кайл Пикок, инженер-программист | DFINITY

Интернет-компьютерный блокчейн позволяет разработчикам создавать масштабируемые веб-интерфейсы, используя канистровые смарт-контракты, полностью на общедоступном блокчейне, и обслуживать их напрямую конечным пользователям через веб-браузеры.

Этот пост представляет собой пошаговое руководство по созданию интерфейсного dapp на Интернет-компьютере с использованием преимуществ новых функций, которые он поддерживает. Если это кажется простым, отлично! Мы работали над тем, чтобы сделать этот рабочий процесс доступным для разработчиков блокчейнов и особенно инженеров, которые уже привыкли размещать статические ресурсы в корзинах Netlify, Fastly или S3. К концу этого руководства ваше приложение или веб-сайт присоединится к более чем 1000 других веб-сайтов, уже работающих на компьютере в Интернете.

Для этой демонстрации я буду использовать Gatsby.js.

Начиная

Для начала запускаю команду npm init gatsby. Меня спрашивают: "Как бы вы хотели назвать свой сайт?" Я назову эту книгу "contact_book". Я также буду использовать это имя для соответствующей папки.

Затем меня спрашивают, буду ли я использовать CMS, и я отвечаю: «Нет». Я выберу «стилизованные компоненты» в командной строке системы стилей в качестве предпочтения, пропущу другие дополнительные функции, и тогда мы приступим к новому проекту!

Это создаст мне простую файловую структуру:

├── README.md
├── gatsby-config.js
├── package-lock.json
├── package.json
└── src
    ├── images
    │   └── icon.png
    └── pages
        ├── 404.js
        └── index.js

Развернуть статический сайт

Мы можем запустить проект с помощью webpack-dev-server, запустив npm run develop, и мы можем скомпилировать проект в статические ресурсы HTML, CSS и JavaScript с помощью команды npm run build.

Чтобы разместить проект на Интернет-компьютере, нам необходимо сделать следующее:

  • Создайте и настройте файл dfx.json в корне проекта.
  • Установите dfx SDK
  • Разверните с помощью команды dfx deploy

Создание dfx.json

Поскольку Gatsby компилирует вывод сборки в общедоступный каталог, наш файл dfx.json будет выглядеть так:

// dfx.json
{
  "canisters": {
    "www": {
      "type": "assets",
      "source": ["public"]
    }
  }
}

Установка dfx

Следуйте инструкциям на https://dfinity.org/developers/, чтобы установить SDK. Мы поддерживаем Mac, Linux и Windows с помощью WSL или VirtualBox.

Разверните свой сайт

Начните с запуска npm run build, чтобы скомпилировать свой веб-сайт. Теперь вы можете запустить dfx deploy --network ic --no-wallet из того же каталога, что и ваш dfx.json, чтобы опубликовать свой веб-сайт на компьютере в Интернете.

После завершения развертывания сайта вы можете найти свой идентификатор накопителя, запустив dfx canister id www и перейдя по адресу https: // {canisterId} .ic0.app.

Как пополнить свою первую канистру

Теперь у нас есть руководство по оплате и настройке вашей первой канистры. Базовый поток (на данный момент) в основном выглядит так:

  • Используйте ICP в интерфейсном dapp NNS для создания контейнера
  • Свяжите эту канистру с вашим принципалом dfx
  • Развернуть в существующую канистру
  • При необходимости долейте канистру циклами, используя интерфейс NNS.

Сделай передышку

Поздравляем - вы развернули свое первое веб-приложение Internet Computer! Есть очень хорошие шансы, что это ваше первое децентрализованное приложение, построенное на технологии блокчейн, и это стоит отметить. Вы увидите, что все ваши ресурсы, от HTML до изображений, работают нормально, как если бы вы вытащили их прямо со статического сервера Nginx или PHP старой школы.

Настройка dapp

Теперь давайте немного настроим здесь код. Я хочу создать книгу контактов, поэтому давайте заменим логику в src / pages / index.js на нашу новую логику dapp.

По сути, у нас есть форма, которая позволяет пользователю создавать контакт, ввод для поиска контактов по адресу электронной почты и компонент для отображения сохраненного контакта.

Есть несколько способов сохранить эту информацию. Сначала я мог бы начать с записи данных в localStorage, Firebase или MongoDB Atlas в виде простого текста, закодированного с помощью JSON.stringify (). Сегодня я сохраню эти данные с помощью смарт-контракта контейнера на Интернет-компьютере.

Добавление серверной части

Нам нужно будет внести несколько изменений, чтобы модифицировать наш проект и добавить серверную канистру.

  • Добавляем исходный код для нашего контракта
  • Настройте dfx.json для внутреннего накопителя
  • Настройте Gatsby на псевдоним нашего кода, сгенерированного dfx
  • Используйте пакет npm @ dfinity / agent для выполнения вызовов на серверную часть с помощью Actor
  • Подключите Актера к нашей логике децентрализованного приложения

Добавление нашей внутренней логики

Я создам контейнер Motoko, который будет реализовывать простую логику настройки и получения информации, хранящейся в HashMap.

// Main.mo
import HM "mo:base/HashMap";
import Text "mo:base/Text";
import Error "mo:base/Error";
import Iter "mo:base/Iter";


actor {
  stable var entries : [(Text, Text)] = [];

  let store: HM.HashMap<Text, Text> = HM.fromIter(entries.vals(), 16, Text.equal, Text.hash);

  /// returns null if there was no previous value, else returns previous value
  public shared func set(k:Text,v:Text): async ?Text {
    if(k == ""){
      throw Error.reject("Empty string is not a valid key");
    };
    return store.replace(k, v);
  };

  public query func get(k:Text): async ?Text {
    return store.get(k);
  };

  system func preupgrade() {
    entries := Iter.toArray(store.entries());
  };

  system func postupgrade() {
    entries := [];
  };

};

Не вдаваясь в подробности здесь, этот код использует стабильную переменную для сохранения наших данных при обновлениях и инициализирует удобный интерфейс HashMap, который хранит данные с ключом типа Text и значением типа Text.

Затем мы реализуем наш набор и получаем интерфейсы и добавляем хуки перед обновлением и после обновления, снова для сохранения данных между обновлениями.

Я сохраню это в новой папке в моем каталоге src по адресу src / backend / contact_book / Main.mo. Этот код написан на Motoko, языке, поддерживаемом Dfinity специально для работы с функциями Интернет-компьютера. IC поддерживает любой язык, который может компилироваться в веб-сборку, а Rust и C - другие популярные варианты разработки накопителей. Motoko - это язык с открытым исходным кодом, и вы можете узнать о нем больше здесь.

Настроить dfx.json

Теперь нам нужно настроить dfx, чтобы он знал о нашей новой канистре. Мы добавим для него новый объект-накопитель и свяжем его как зависимость для нашего внешнего накопителя. Это выглядит примерно так:

// dfx.json
{
  "canisters": {
    "contact_book": {
      "main": "src/backend/contact_book/Main.mo"
    },
    "www": {
      "dependencies": ["contact_book"],
      "type": "assets",
      "source": ["public"]
    }
  }
}

Настроить Гэтсби

Затем нам нужно будет обновить Gatsby псевдонимом, который указывает на динамически сгенерированный код из dfx, который будет расположен в скрытой папке .dfx в вашем проекте. Мы создадим файл gatsby-node.js в корне нашего проекта и напишем код, который будет использовать наши настройки в dfx.json для импорта наших пользовательских интерфейсов для нашей новой серверной части.

// gatsby-node.js
const dfxJson = require("./dfx.json");
const webpack = require("webpack");
const path = require("path");

const aliases = Object.entries(dfxJson.canisters).reduce(
  (acc, [name, _value]) => {
    // Get the network name, or `local` by default.
    const networkName = process.env["DFX_NETWORK"] || "local";
    const outputRoot = path.join(
      __dirname,
      ".dfx",
      networkName,
      "canisters",
      name
    );

    return {
      ...acc,
      ["dfx-generated/" + name]: path.join(outputRoot, name + ".js"),
    };
  },
  {}
);

exports.onCreateWebpackConfig = ({ stage, actions }) => {
  actions.setWebpackConfig({
    resolve: {
      alias: aliases,
    },
    plugins: [
      new webpack.ProvidePlugin({
        Buffer: [require.resolve("buffer/"), "Buffer"],
      }),
    ],
  });
};

Кроме того, мы добавим прокси в файл gatsby-config.js, проксирующий localhost: 8000, который является адресом по умолчанию для нашей реплики dfx.

// gatsby-config.js
module.exports = {
  siteMetadata: {
    title: "contact book",
  },
  plugins: ["gatsby-plugin-styled-components"],
  proxy: {
    prefix: "/api",
    url: "http://localhost:8000",
  },
};

Использование @ dfinity / agent

Теперь, когда мы присвоили нашим ресурсам, сгенерированным dfx, псевдонимы, мы можем импортировать их и использовать в нашей кодовой базе. Итак, теперь я создам src /actor.js и импортирую @ dfinity / agent из / contact_book, созданного dfx.

// actor.js
import { Actor, HttpAgent } from "@dfinity/agent";
import {
  idlFactory,
  canisterId,
} from "dfx-generated/contact_book";

const agent = new HttpAgent();
const actor = Actor.createActor(idlFactory, { agent, canisterId });

export default actor;

Здесь мы создаем агент и передаем его конструктору Actor вместе с idlFactory и canisterId из нашего кода, сгенерированного из внутреннего интерфейса.

Затем мы экспортируем наш Actor, у которого есть два метода (set и get), которые уже настроены с API на основе обещаний, чтобы делать безопасные по типу вызовы к нашей серверной части накопителя.

Наконец, мы подключаем его

Мы изменим нашу страницу index.js, добавив логику для хранения отправленных материалов из формы в нашем контейнере с использованием поля электронной почты.

Я импортирую Actor (выполняя это как динамический импорт, чтобы избежать инициализации HttpAgent во время рендеринга на стороне сервера для Gatsby).

React.useEffect(() => {
  import("../actor").then((module) => {
    setActor(module.default);
  });
}, []);

В течение handleSubmit мы будем использовать наш метод set для сохранения данных, а затем очистим нашу контактную форму.

actor?.set(email, JSON.stringify(card.toJSON())).then(() => {
  alert("card uploaded!");
  inputs.forEach((input) => {
    input.value = "";
  });
  setImage("");
});

А затем мы будем использовать метод get для получения контактов с помощью поиска по электронной почте.

actor?.get(email).then((returnedCard) => {
  if (!returnedCard.length) {
    return alert("No contact found for that email");
  }
  setCard(vCard.fromJSON(returnedCard[0]));
  console.log(returnedCard);
});

И теперь у нас есть полностью функционирующее приложение, которое мы можем запустить на Интернет-компьютере!

Подведение итогов

Теперь, когда мы адаптировали нашу кодовую базу, структура нашего проекта выглядит следующим образом:

├── README.md
├── dfx.json
├── gatsby-config.js
├── gatsby-node.js
├── package-lock.json
├── package.json
└── src
    ├── actor.js
    ├── backend
    │   └── contact_book
    │       └── Main.mo
    ├── images
    │   └── icon.png
    └── pages
        ├── 404.js
        └── index.js

Мы можем протестировать изменения локально, запустив dfx deploy. Это создаст и загрузит нашу серверную часть в локальную реплику. Как только это будет завершено, мы снова сможем запустить наш интерфейс с npm run develop -- --port 3000, используя все полезные функции разработки, такие как горячая перезагрузка модуля. Мы указываем порт, поскольку Гэтсби также по умолчанию использует порт 8000.

Если все пойдет хорошо, вы сможете протестировать децентрализованное приложение локально, отправив, а затем получив контакт с помощью пользовательского интерфейса.

Так и должно быть! Вы можете попробовать эти шаги самостоятельно, загрузить этот пример проекта с https://github.com/krpeacock/ic-vcf-gatsby или использовать это руководство в качестве справочника, чтобы начать работу со своими собственными проектами. Нам не терпится увидеть, что вы создаете!
____

Начните создавать на smartcontracts.org и присоединяйтесь к нашему сообществу разработчиков на forum.dfinity.org.