Подключаем React

<< Часть 2 Взаимодействие с Figma

В предыдущих двух статьях я описал, как без особых усилий можно начать писать плагин для Figma. И для того, чтобы сделать простой плагин, этого знания вполне достаточно. Но вот если интерфейс вашего плагина более сложный, чем пара кнопок и инпутов, если наворочена логика состояний, такой подход становится неудобен. Особенно учитывая, что весь код надо писать в html. Решить данную проблему нам поможет его величество React и его преосвященство Webpack. Подключение к плагину обеих технологий хорошо описаны в официальной документации Figma. И можно было не писать эту статью если бы не одно но, официальная инструкция не работает. А раз так, поехали!

Проект я разворачиваю на node версии 17.1.0 и npm версии 8.1.2

И так, открываем проект и в терминале выполняем команду:

npm init

Далее устанавливаем инструменты разработки

npm install --save-dev css-loader html-webpack-inline-source-plugin@beta html-webpack-plugin copy-webpack-plugin style-loader file-loader url-loader webpack webpack-cli babel-loader @babel/preset-env @babel/preset-react       

Устанавливаем react

npm install --save react-dom react

Теперь создадим в корне проекта папку src и в ней следующую иерархию файлов:

Рассмотрим содержимое этих файлов. Начнем с manifest.json

{
 	"name": "Figma Plugin with React",
 	"api": "1.0.0",
 	"main": "./plugin.js",
 	"ui": "./ui.html",
 	"editorType": [
 	  "figma"
 	]
} 

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

Теперь взглянем на ui.html. В него мы добавляем body, чуть позже поясню зачем, а так же контейнер для нашего проекта.

<body>
   <div id="figma-plugin-container"></div>
</body>

Следующим идет ui.js

import React from 'react';
import ReactDom from 'react-dom';
import App from './App';

ReactDom.render(
   <App />,
   document.querySelector('#figma-plugin-container')
);

Тут мы импортируем react и react-dom, а также модуль App, который рендерим в контейнере проекта.

В файле plugin.js мы пока что только отобразим окно плагина.

figma.showUI (
   __html__,
   {width: 400, height: 400}
);

Файл App/index.js и App/module.css пока что тоже оставим дефолтными, наполнять их будем позже

App/index.js

import React from "react";

export default function App() {
   return (
       <div className="App">
          
       </div>
   )
}

App/module.css

body, html {
   padding: 0;
   margin: 0;
   width: 100%;
   height: 100%;
}

.app {
   box-sizing: border-box;
   width: 100%;
   height: 100%;
   padding: 10px;
}

Каркас проекта готов. Теперь создадим  в корне файл webpack.config.js следующего содержания.

const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const path = require('path');

module.exports = (env, argv) => ({
   mode: argv.mode === 'production' ? 'production' : 'development',

   devtool: argv.mode === 'production' ? false : 'inline-source-map',

   entry: {
       ui: './src/ui.js',
   },

   module: {
       rules: [
           {
               test: /\.m?js$/,
               exclude: /node_modules(?!\/ansi-regex)(?!\/strip-ansi)/,
               use: {
                   loader: 'babel-loader',
                   options: {
                       presets: [
                           '@babel/preset-env',
                           '@babel/react',
                           {
                               plugins: ['@babel/plugin-proposal-class-properties'],
                           },
                       ],
                   },
               },
           },
           { test: /\.css$/, use: ['style-loader', { loader: 'css-loader' }] },
           { test: /\.(png|jpg|gif|webp|svg)$/, loader: 'url-loader' },
           {
               test: /\.(png|jpe?g|gif)$/i,
               loader: 'file-loader',
           }
       ],
   },

   resolve: {
       extensions: ['.jsx', '.js'],
       modules: [
           path.resolve(__dirname, 'node_modules'),
           path.resolve(__dirname, 'src'),
           path.resolve(__dirname, ''),
       ],
   },

   output: {
       filename: '[name].js',
       publicPath: "/",
   },

   plugins: [
       new HtmlWebpackPlugin({
           template: "./src/ui.html",
           filename: "ui.html",
           inlineSource: ".(js)$",
           inject: "body",
           cache: false,    
       }),
       new HtmlWebpackInlineSourcePlugin(HtmlWebpackPlugin),
       new CopyWebpackPlugin({
           patterns: [{
               from: './src/plugin.js',
               to: '',
           }, {
               from: './src/manifest.json',
               to: '',
           }],
       })
   ],
})

Не стану подробно его расписывать. Если у вас вызывает недоумение этот скрипт, предлагаю ознакомиться с официальной документацией webpack. Обращу только ваше внимание на строку

inject: "body"

отсутствие которой в официальном гайде Figma вызывает ошибку плагина HtmlWebpackInlineSourcePlugin. Вот собственно зачем нам понадобился body в ui.html. Туда webpack поместит скрипт из файла ui.js.

Вот собственно и все, нам осталось только добавить в package.json  пару скриптов

"scripts": {
   "start": "npx webpack --mode=development --watch",
   "build": "npx webpack --mode=production"
}

Скрипт start запустит сборку в режиме development, а флаг -watch указывает на необходимость пересобрать проект при изменениях. Срипт же build сделает сборку в режиме production, а значит готовую к публикации.

Заходим в терминал. Запускаем команду

npm run start

В проекте появится папка dist со сборкой плагина. Импортировав плагин из файла manifest.json этой папки, вы получите свой плагин, собранный на основе react.

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

Комментарии (6)


  1. eggstream
    23.11.2021 13:15

    Современные фронтовики и шагу не ступят без 100500 монструозных библиотек и фреймворков, даже там, где можно было бы обойтись парой строчек на чистом HTML/CSS/JS


    1. kay-n Автор
      23.11.2021 13:45

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


      1. eggstream
        23.11.2021 16:06

        А разве редакс не является монструозным дополнением к монструозному реакту исключительно для реализации довольно простенькой по функциональности машины состояний?

        Если что, на ванилле можно самому написать машину состояний в сотню строк или даже меньше


        1. kay-n Автор
          23.11.2021 16:34

          Да кто же спорит? Можно и код в блокноте писать. Многие вовсе опрощаются поближе к земле, к корням так сказать. Мне тоже пару раз в год приходится на es3 писать, AfterEffects по прежнему поддерживает только эту версию. Но статья о написании инструмента, в моем случае для коллег дизайнеров. Задача инструмента быть удобным для пользователя и в кратчайшие сроки попасть к нему в руки. Быть оптимальным у инструмента задачи нет.


          1. eggstream
            23.11.2021 16:38

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


            1. kay-n Автор
              23.11.2021 16:43

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