7. Скафолдинг внешнего интерфейса

Перевод | Автор оригинала: Loris

ЭПИЗОД 7

2 МЕСЯЦА НАЗАД

ЧТЕНИЕ 14 МИН.

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

Мы хотим абстрагировать все это в приятный пользовательский интерфейс (UI), который напоминает то, с чем они знакомы. По этой причине мы создадим внешний клиент, используя VueJS.

Мы будем использовать VueJS, потому что: а) это мой любимый фреймворк JavaScript и б) он очень мало документирован в экосистеме Solana. Если вы лучше знакомы с другими фреймворками JavaScript, такими как React, вы все равно можете следить за ними, так как большинство концепций перекликаются между фреймворками.

Теперь фронтенд-разработка — это отдельный мир, и я легко мог бы часами подробно описывать, как создать пользовательский интерфейс, который мы получим в конце этого эпизода. Тем не менее, в центре внимания этой серии Солана, и я не хотел бы слишком отклоняться от нее. Существует множество руководств по разработке внешнего интерфейса — даже в этом блоге.

В то же время нам нужен пользовательский интерфейс, чтобы продолжить наше путешествие и создать наше децентрализованное приложение. Так вот в чем дело. В этом эпизоде я объясню, как начать работу с VueJS и установить все зависимости, которые нам понадобятся, чтобы вы могли сделать это самостоятельно. Затем, когда дело доходит до фактического дизайна и компонентов пользовательского интерфейса, я дам вам кучу файлов для копирования/вставки в разные места и кратко объясню, что они делают. Сначала компоненты будут содержать фиктивные данные, чтобы мы могли связать их с нашей программой Solana в следующих эпизодах.

Пристегните ремни безопасности, потому что мы собираемся двигаться быстро. Пойдем!

Установите интерфейс командной строки Vue.

Один из самых простых способов создать новое приложение VueJS — использовать его инструменты CLI.

Если они у вас еще не установлены, вы можете сделать это, запустив следующее.

npm install -g @vue/cli@5.0.0-rc.1

Обратите внимание, что мы явно запрашиваем версию 5, которая все еще является кандидатом на выпуск на момент написания, потому что мы хотим, чтобы наше приложение VueJS было связано с Webpack 5 вместо Webpack 4.

Вы можете проверить правильность установки инструментов VueJS CLI, запустив:

vue --version

Создайте новое приложение Vue

Теперь мы можем создать новое приложение VueJS, запустив vue create, а затем каталог, который должен быть создан для него.

Мы хотим, чтобы наш внешний клиент находился в каталоге app, который в настоящее время является пустой папкой. Поэтому нам также нужно будет использовать параметр --force для его переопределения. Хорошо, давайте запустим это.

vue create app --force

Теперь вас должны попросить выбрать предустановку для вашего приложения. В этой серии мы будем использовать Vue 3, поэтому давайте выберем предустановку Default (Vue 3).

? Please pick a preset:
  Default ([Vue 2] babel, eslint)
 Default (Vue 3) ([Vue 3] babel, eslint)        # <- This one.
  Manually select features

И вот так мы получили приложение VueJS 3 внутри нашего проекта.

Давайте cd в него, так как мы будем работать внутри этого каталога до конца этого эпизода.

cd app

Установка библиотек Solana и Anchor

Далее давайте установим библиотеки JavaScript, предоставленные Solana и Anchor. Мы упоминали в предыдущем эпизоде, что они уже были включены в наш проект Anchor для наших тестов, но это другая среда со своими собственными зависимостями, поэтому нам нужно установить их явно.

Убедитесь, что находитесь в каталоге app и запустите следующее.

npm install @solana/web3.js @project-serum/anchor

Настройка полифиллов node.js

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

Некоторые из библиотек JavaScript, которые мы будем использовать в нашем приложении, зависят от полифиллов Node.js.

Node.js — это, по сути, «JavaScript для серверов», и цель полифилов Node.js — перенести некоторые из его основных зависимостей в мир внешнего интерфейса. Таким образом, один и тот же код можно использовать на обеих сторонах.

Например, помните, как мы преобразовали строку в буфер, используя Buffer.from('some string')? Нам не нужно было импортировать этот объект Buffer, потому что это основная зависимость Node.js, которая была заполнена для нас.

В настоящее время мир внешнего интерфейса отказывается от объединения всех этих зависимостей Node.js по умолчанию. И это именно то, что сделал Webpack, когда выпустил версию 5. Вот очень хорошее объяснение [из их документации](https://webpack.js.org/blog/2020-10-10-webpack-5-release/#automatic- nodejs-полифиллы удалены):

В первые дни целью веб-пакета было разрешить запуск большинства модулей Node.js в браузере, но ландшафт модулей изменился, и теперь многие модули используются в основном для целей внешнего интерфейса. Webpack <= 4 поставляется с полифиллами для многих основных модулей Node.js, которые автоматически применяются, как только модуль использует любой из основных модулей (т. е. модуль шифрования).

Webpack 5 прекращает автоматическое заполнение этих основных модулей и фокусируется на модулях, совместимых с внешним интерфейсом. Наша цель — улучшить совместимость с веб-платформой, где основные модули Node.js недоступны.

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

BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.

К счастью для нас, есть способ решить эту проблему, вернув нужные нам полифиллы и/или сказав Webpack, что они нам не нужны, чтобы он перестал жаловаться.

В нашем случае нам понадобится только полифил Buffer, и мы можем отключить другие, которые в противном случае потерпели бы неудачу. Мы можем сделать это внутри нашего файла vue.config.js, который содержит свойство configureWebpack, позволяющее нам предоставлять дополнительные конфигурации Webpack.

const webpack = require('webpack')
const { defineConfig } = require('@vue/cli-service')

module.exports = defineConfig({
    transpileDependencies: true,
    configureWebpack: {
        plugins: [
            new webpack.ProvidePlugin({
                Buffer: ['buffer', 'Buffer']
            })
        ],
        resolve: {
            fallback: {
                crypto: false,
                fs: false,
                assert: false,
                process: false,
                util: false,
                path: false,
                stream: false,
            }
        }
    }
})

Круто! Теперь мы должны быть защищены от запутанных ошибок полифилла. 😌

Настройка ESLint

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

Поскольку мы будем использовать суперпричудливый тег <script setup> в наших компонентах VueJS 3, нам нужно сообщить ESLint об этом, поэтому наш редактор кода не показывает много ошибок, когда код действительно действителен.

Здесь не нужно слишком беспокоиться о деталях, просто откройте package.json вашего каталога app и замените ваш объект eslintConfig следующим.

"eslintConfig": {
  "root": true,
  "env": {
    "node": true,
    "vue/setup-compiler-macros": true
  },
  "extends": [
    "plugin:vue/vue3-essential",
    "eslint:recommended"
  ],
  "parserOptions": {
    "parser": "@babel/eslint-parser"
  },
  "rules": {
    "vue/script-setup-uses-vars": "error"
  }
},

Установка TailwindCSS

Для разработки пользовательского интерфейса я буду использовать свою любимую структуру CSS: TailwindCSS. Если вы не знакомы с ним, это сверхмощный служебный фреймворк, с которым очень приятно работать. Излишне говорить, что я очень рекомендую это.

Для его установки нам понадобятся следующие зависимости. Как обычно, обязательно запустите это в каталоге app.

npm install tailwindcss@latest postcss@latest autoprefixer@latest

Затем нам нужно сгенерировать наш файл конфигурации Tailwind. Для этого просто запустите следующее.

npx tailwindcss init -p

Это сгенерировало файл tailwind.config.js в нашем каталоге app.

Обратите внимание, что мы использовали параметр -p для создания файла postcss.config.js. Это необходимо для того, чтобы Webpack мог распознать Tailwind как плагин PostCSS и, следовательно, скомпилировать наши конфигурации Tailwind.

Давайте сразу внесем небольшую корректировку в наш конфиг-файл Tailwind. Мы предоставим массив purge, чтобы при компиляции для производства Tailwind мог удалить все служебные классы, которые не используются в предоставленных путях.

По сути, нам нужно указать, где находится наш HTML, который в нашем случае находится внутри любого файла JavaScript в папке src или в общедоступном файле index.html.

Итак, откройте файл tailwind.config.js и замените пустой массив purge следующими строками

module.exports = {
  purge: [
    './public/index.html',
    './src/**/*.{vue,js,ts,jsx,tsx}',
  ],
  // ...
}

Затем создайте новый файл в папке src с именем main.css и добавьте следующий код.

@tailwind base;
@tailwind components;
@tailwind utilities;

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

Наконец, нам нужно импортировать этот новый файл CSS в наш файл main.js, чтобы Webpack мог его подобрать.

Давайте импортируем его в начало этого файла и добавим несколько комментариев, чтобы разделить код на небольшие разделы.

// CSS.
import './main.css'

// Create the app.
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')

We’re now fully ready to use TailwindCSS!

Установка Vue маршрутизатора

Далее нам нужна маршрутизация в нашем интерфейсе. Щелчок по новой странице должен отражаться в URL-адресе и наоборот. К счастью, нам не нужно реализовывать это с нуля, поскольку для этой цели мы можем использовать Vue Router.

Чтобы установить его, нам нужно запустить следующее. Обратите внимание, что нам нужно явно установить версию 4 Vue Router, так как эта версия совместима с Vue 3.

npm install vue-router@4

Далее давайте определим наши маршруты — то есть сопоставление между URL-адресами и компонентами VueJS.

Создайте новый файл в папке src с именем route.js и вставьте внутрь следующее.

export default [
    {
        name: 'Home',
        path: '/',
        component: require('@/components/PageHome').default,
    },
    {
        name: 'Topics',
        path: '/topics/:topic?',
        component: require('@/components/PageTopics').default,
    },
    {
        name: 'Users',
        path: '/users/:author?',
        component: require('@/components/PageUsers').default,
    },
    {
        name: 'Profile',
        path: '/profile',
        component: require('@/components/PageProfile').default,
    },
    {
        name: 'Tweet',
        path: '/tweet/:tweet',
        component: require('@/components/PageTweet').default,
    },
    {
        name: 'NotFound',
        path: '/:pathMatch(.*)*',
        component: require('@/components/PageNotFound').default,
    },
]

Это все страницы, которые содержит наше приложение, включая резервные URL-адреса, которые не существуют.

Если мы попытаемся скомпилировать наше внешнее приложение на этом этапе, используя npm run serve, это не удастся, потому что все эти компоненты отсутствуют, но не волнуйтесь, мы добавим их все в следующем разделе.

Теперь, когда наши маршруты определены, мы можем импортировать и подключить плагин Vue Router к нашему приложению VueJS.

Откройте файл src/main.js и обновите его следующим образом.

// CSS.
import './main.css'

// Routing.
import { createRouter, createWebHashHistory } from 'vue-router'
import routes from './routes'
const router = createRouter({
    history: createWebHashHistory(),
    routes,
})

// Create the app.
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).use(router).mount('#app')

Как видите, мы сначала создаем экземпляр маршрутизатора, предоставляя наши маршруты, а затем заставляем наше приложение VueJS «использовать» его в качестве подключаемого модуля.

Если вам интересно, метод createWebHashHistory добавляет ко всем путям префикс #, чтобы нам не нужно было позже настраивать какие-либо перенаправления на нашем сервере.

На данный момент наше приложение VueJS полностью настроено с помощью Vue Router и TailwindCSS. Все, что осталось сделать, это реализовать компоненты, которые составят пользовательский интерфейс нашего интерфейса.

Это означает, что если вы хотите создать свой собственный дизайн, вы можете остановиться здесь и реализовать компоненты, перечисленные в файле route.js, самостоятельно.

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

Итак, пришло время для копирования/вставки!

Исходники

Хорошо, давайте сделаем это! Загрузите ZIP-файл ниже и распакуйте его, чтобы получить доступ ко всем файлам, которые составят наш пользовательский интерфейс.

Загрузить ZIP-файл

Теперь, когда у вас есть все файлы, давайте переместим их в нужные папки.

Внутри папки src:

Бум, интерфейс готов! 💥

На этом этапе вы сможете запустить npm run serve и взглянуть на пользовательский интерфейс, открыв: http://localhost:8080/.

npm run serve

# Outputs:
#
#  App running at:
#  - Local:   http://localhost:8080/
#  - Network: http://192.168.68.118:8080/

Хорошо, давайте немного осмотримся и объясним назначение всех этих файлов, которые мы только что добавили.

Объяснение компонентов

Начнем с компонентов. Помимо компонента App.vue, который находится в папке src, все остальные компоненты должны находиться в папке src/components.

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

Объяснение файлов API

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

На данный момент каждый из этих файлов определяет функцию, которая возвращает фиктивные данные.

Объяснение композиций

В этом ZIP-файле есть еще одна папка, которую нужно объяснить: composables.

В VueJS мы называем «составными» функциями, которые используют API композиции для расширения поведения компонента. Если вы знакомы с React, это можно сравнить с хуками React для VueJS.

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

Вывод

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

Говоря о репозиториях, вы можете просмотреть код этого эпизода в ветке episode-7 и сравнить код с предыдущим эпизодом, как обычно. На этот раз я также добавил еще одну ссылку для сравнения после коммита, который сгенерировал внешний интерфейс через vue create app, чтобы вы могли увидеть, что мы изменили впоследствии.

Просмотреть эпизод 7 на GitHub

Сравните с Эпизодом 6/[Сравните после создания приложения VueJS](https://github.com /lorisleiva/solana-twitter/compare/1d7642e092a5946e6f08f5b90cadc612051b9b75...эпизод-7)

В следующих трех эпизодах мы соединим наш фиктивный пользовательский интерфейс с реальными данными и с реальными взаимодействиями с нашей программой Solana. Мы начнем с интеграции нашего интерфейса с кошельками Solana, такими как Phantom, чтобы мы могли идентифицировать подключенного пользователя в нашем приложении. Увидимся в следующем выпуске!