Vue 3를 사용하여 Fimga 플러그인을 개발하는 과정을 기록합니다.

青灯夜游
풀어 주다: 2022-04-11 20:53:52
앞으로
3522명이 탐색했습니다.

Figma 플러그인을 개발하기 위해 Vue 3를 사용하는 방법은 무엇입니까? 다음 글에서는 Figma 플러그인의 원리를 소개하고, Vue 3를 사용하여 Fimga 플러그인을 개발하는 과정을 기록하고, Out-of-box 코드를 첨부해 보도록 하겠습니다.

Vue 3를 사용하여 Fimga 플러그인을 개발하는 과정을 기록합니다.

Vue 3을 사용하여 Figma 플러그인 개발

Figma는 요즘 인기 있는 디자인 도구이며 점점 더 많은 디자인 팀이 Sketch에서 Figma로 전환하기 시작했습니다. Figma의 가장 큰 특징은 웹 기술을 사용하여 개발되었으며 완전한 크로스 플랫폼이라는 점입니다. Figma 플러그인도 웹 기술을 사용하여 개발됩니다. html, jscss를 알고 있으면 Figma 플러그를 작성할 수 있습니다. -안에. Figma 플러그인 원리htmljscss 就能动手写一个 Figma 插件。

Figma 插件原理

Figma 架构简介

介绍 Fimga 插件之前,我们先来了解一下 Fimga 的技术架构。

Figma 整体是用 React 开发的,核心的画布区是一块 canvas ,使用WebGL来渲染。并且画布引擎部分使用的是WebAssembly,这就是 Figma 能够如此流畅的原因。桌面端的Figma App 使用了 Electron——一个使用Web技术开发桌面应用的框架。Electron 类似于一个浏览器,内部运行的其实还是一个Web应用。

Figma 插件原理

在Web端开发一套安全、可靠的插件系统, iframe 无疑是最直接的方案。 iframe 是标准的W3C规范,在浏览器上已经经过多年应用,它的特点是:

  • 安全,天然沙箱隔离环境,iframe内页面无法操作主框架;

  • 可靠,兼容性非常好,且经过了多年市场的检验;

但是它也有明显的缺点:与主框架通信只能通过 postMessage(STRING) 的方式,通信效率非常低。如果要在插件里操作一个画布元素,首先要将元素的节点信息从主框架拷贝到 iframe 中,然后在  iframe 中操作完再更新节点信息给主框架。这涉及到大量通信,而且对于复杂的设计稿节点信息是非常巨大的,可能超过通信的限制。

为了保证操作画布的能力,必须回到主线程。插件在主线程运行的问题主要在于安全性上,于是Figma的开发人员在主线程实现了一个 js 沙箱环境,使用了Realm API。沙箱中只能运行纯粹的 js 代码和Figma提供的API,无法访问浏览器API(例如网络、存储等),这样就保证了安全性。

Vue 3를 사용하여 Fimga 플러그인을 개발하는 과정을 기록합니다.

感兴趣的同学推荐阅读官方团队写的《How to build a plugin system on the web and also sleep well at night》,详细介绍了 Figma 插件方案的选择过程,读来获益良多。

经过综合考虑,Figma 将插件分成两个部分:插件UI运行在 iframe 中,操作画布的代码运行在主线程的隔离沙箱中。UI线程和主线程通过  postMessage 通信。

插件配置文件 manifest.json 中分别配置 main 字段指向加载到主线程的 js 文件, ui 字段配置加载到 iframe 中的 html 文件。打开插件时,主线程调用 figma.showUI() 方法加载 iframe

写一个最简单的 Figma 插件

为了了解插件的运行过程,我们先写一个最简单的 Figma 插件。功能很简单:可以加减正方形色块。

安装Figma桌面端

首先要下载并安装好 Figma 桌面端。

编写插件的启动文件 manifest.json

新建一个代码工程,在根目录中新建 manifest.json

Figma 아키텍처 소개🎜

🎜 Fimga 플러그인 소개 그 전에 먼저 Fimga의 기술 아키텍처를 살펴보겠습니다. 🎜🎜Figma 전체는 React🎜를 사용하여 개발되었습니다. 🎜WebGL🎜을 사용하여 렌더링된 캔버스 조각입니다. 그리고 캔버스 엔진 부분은 🎜WebAssembly🎜를 사용하기 때문에 Figma가 매우 매끄러울 수 있습니다. 데스크탑 Figma 앱은 🎜Electron🎜🎜 - 웹 기술 프레임워크를 사용합니다. 데스크톱 애플리케이션 개발. Electron은 브라우저와 유사하지만 실제로는 내부적으로 웹 애플리케이션을 실행합니다. 🎜

🎜Figma 플러그인 원리🎜

🎜웹에서 안전하고 신뢰할 수 있는 플러그인 시스템을 개발하려면 iframe이 의심할 여지 없이 최고입니다. 가장 직접적인 해결책. iframe은 수년 동안 브라우저에서 사용되어 온 표준 W3C 사양입니다. 그 특징은 다음과 같습니다. 🎜
  • 🎜보안, 자연스러운 샌드박스 격리 환경, iframe 내의 페이지는 메인 페이지 프레임워크; 🎜
  • 🎜신뢰할 수 있고 호환성이 매우 좋으며 수년 동안 시장에서 테스트되었습니다. 🎜
🎜그러나 또한 명백한 단점도 있습니다. 메인 프레임워크는 postMessage(STRING) 메소드를 통해서만 가능하므로 통신 효율성이 매우 낮습니다. 플러그인에서 캔버스 요소를 조작하려면 먼저 메인 프레임에서 해당 요소의 노드 정보를 iframe에 복사한 후 iframe에서 작업을 완료한 후 노드 정보를 업데이트해야 합니다. code>iframe 메인 프레임. 여기에는 많은 의사소통이 필요하며, 복잡한 설계 초안의 경우 노드 정보가 매우 커서 의사소통 한계를 초과할 수 있습니다. 🎜🎜캔버스 작동 기능을 보장하려면 메인 스레드로 돌아가야 합니다. 메인 스레드에서 실행되는 플러그인의 주요 문제는 보안이므로 Figma 개발자는
영역 API🎜. 샌드박스에서는 순수한 js 코드와 Figma에서 제공하는 API만 실행할 수 있으며, 브라우저 API(네트워크, 스토리지 등)에 접근할 수 없어 보안이 보장됩니다. 🎜🎜Vue 3를 사용하여 Fimga 플러그인을 개발하는 과정을 기록합니다.🎜🎜 느낌 관심 있는 학생은 공식 팀 "웹에서 플러그인 시스템 구축하고 밤에도 푹 자는 방법"🎜, Figma 플러그인 솔루션 선택 과정을 자세히 소개하고, 그것을 읽으면 많은 유익을 얻을 수 있습니다. 🎜🎜종합적인 고려 끝에 Figma는 플러그인을 두 부분으로 나눕니다. 플러그인 UI는 iframe에서 실행되고, 캔버스를 작동하는 코드는 메인 스레드의 격리 샌드박스에서 실행됩니다. UI 스레드와 메인 스레드는 postMessage를 통해 통신합니다. 🎜🎜메인 스레드에 로드된 js 파일을 가리키도록 플러그인 구성 파일 manifest.jsonmain 필드를 구성하고 ui 필드 구성은 iframe 내의 html 파일에 로드됩니다. 플러그인을 열 때 메인 스레드는 figma.showUI() 메서드를 호출하여 iframe을 로드합니다. 🎜🎜🎜가장 간단한 Figma 플러그인 작성🎜🎜🎜플러그인의 실행 프로세스를 이해하기 위해 먼저 가장 간단한 Figma 플러그인을 작성합니다. -안에. 기능은 간단합니다. 정사각형 색상 블록을 추가하거나 뺄 수 있습니다. 🎜

🎜Figma 데스크톱 설치 🎜

🎜먼저
Figma 데스크톱을 다운로드하고 설치하세요. 🎜

🎜플러그인 시작 파일 매니페스트.json을 작성하세요🎜

🎜새 코드 프로젝트를 만들고 새 manifest.json 파일을 만듭니다. 루트 디렉터리에 내용은 다음과 같습니다. 🎜
{
  "name": "simple-demo",
  "api": "1.0.0",
  "main": "main.js",
  "ui": "index.html",
  "editorType": [
    "figjam",
    "figma"
  ]
}
로그인 후 복사

UI 코드 작성

새 루트 디렉터리 index.html 만들기, index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Demo</title>
  <style>

    h1 {
      text-align: center;
    }
    p {
      color: red;
    }

    .buttons {
      margin-top: 20px;
      text-align: center;
    }

    .buttons button {
      width: 40px;
    }

    #block-num {
      font-size: 20px;
    }

  </style>
 
</head>
<body>
  <h1>Figma 插件 Demo</h1>
  <p>当前色块数量:<span id="block-num">0</span></p>
  <div>
    <button id="btn-add" onclick="addBlock()">+</button>
    <button id="btn-sub" onclick="subBlock()">-</button>
  </div>

  <script>
    console.log(&#39;ui code runs!&#39;);
    var blockNumEle = document.getElementById(&#39;block-num&#39;);
    function addBlock() {
      console.log(&#39;add&#39;);
      var num = +blockNumEle.innerText;
      num += 1;
      blockNumEle.innerText = num;
    }

    function subBlock() {
      console.log(&#39;substract&#39;);
      var num = +blockNumEle.innerText;
      if (num === 0) return;
      num -= 1;
      blockNumEle.innerText = num;
    }
  </script>
</body>
</html>
로그인 후 복사

编辑main js 代码

根目录新建 main.js ,内容如下:

console.log(&#39;from code 2&#39;);
figma.showUI(__html__, {
  width: 400,
  height: 400,
});
로그인 후 복사

启动插件

Figma桌面APP,画布任意地方右键打开菜单, Plugins -> Development -> Import plugin from manifest... ,选择前面创建的 manifest.json

console.log(&#39;figma plugin code runs!&#39;)

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

const nodes = [];

figma.ui.onmessage = (msg) => {=
  if (msg.type === "add-block") {
    const rect = figma.createRectangle();
    rect.x = nodes.length * 150;
    rect.fills = [{ type: "SOLID", color: { r: 1, g: 0.5, b: 0 } }];
    figma.currentPage.appendChild(rect);
    nodes.push(rect);
  } else if (msg.type === "sub-block") {
    const rect = nodes.pop();
    if (rect) {
      rect.remove();
    }
  }
  figma.viewport.scrollAndZoomIntoView(nodes);
};
로그인 후 복사

Plugins -> Development -> simple-demo기본 js 코드 편집

Vue 3를 사용하여 Fimga 플러그인을 개발하는 과정을 기록합니다.새 루트 디렉터리 만들기 루트 디렉토리main.js, 내용은 다음과 같습니다:

function addBlock() {
  console.log(&#39;add&#39;);
  var num = +blockNumEle.innerText;
  num += 1;
  blockNumEle.innerText = num;
  parent.postMessage({ pluginMessage: { type: &#39;add-block&#39; } }, &#39;*&#39;)
}

function subBlock() {
  console.log(&#39;substract&#39;);
  var num = +blockNumEle.innerText;
  if (num === 0) return;
  num -= 1;
  blockNumEle.innerText = num;
  parent.postMessage({ pluginMessage: { type: &#39;sub-block&#39; } }, &#39;*&#39;)
}
로그인 후 복사

플러그인 시작Plugins -> Development -> Open console 可以打开调试控制台。可以看到我们打印的日志。

操作画布

前面讲了,画布代码是运行在主线程的,为了执行效率,插件要操作画布内容也只能在主线程执行,即在 main.js 中。 main.js 中暴露了顶级对象 figma ,封装了用来操作画布的一系列API,具体可以去看官网文档。我们用 figma.createRectangle() 来创建一个矩形。主线程需要通过 figma.ui.onmessage 监听来自UI线程的事件,从而做出响应。修改后的 main.js 代码如下:

npm init vite@latest figma-plugin-vue3 --template vue-ts
cd figma-plugin-vue3
npm install
npm run dev
로그인 후 복사

同时要修改 index.html 中的部分代码,通过 parent.postMessage 给主线程发送事件:

<script setup>
import { ref } from &#39;vue&#39;;

const num = ref(0);

console.log(&#39;ui code runs!&#39;);

function addBlock() {
  console.log(&#39;add&#39;);
  num.value += 1;
  parent.postMessage({ pluginMessage: { type: &#39;add-block&#39; } }, &#39;*&#39;)
}

function subBlock() {
  console.log(&#39;substract&#39;);
  if (num .value=== 0) return;
  num.value -= 1;
  parent.postMessage({ pluginMessage: { type: &#39;sub-block&#39; } }, &#39;*&#39;)
}
</script>

<template>
  <h1>Figma 插件 Demo</h1>
  <p>当前色块数量:<span id="block-num">{{ num }}</span></p>
  <div>
    <button id="btn-add" @click="addBlock">+</button>
    <button id="btn-sub" @click="subBlock">-</button>
  </div>
</template>

<style scoped>
h1 {
  text-align: center;
}
p {
  color: red;
}

.buttons {
  margin-top: 20px;
  text-align: center;
}

.buttons button {
  width: 40px;
}

#block-num {
  font-size: 20px;
}
</style>
로그인 후 복사

重新启动插件,再试验一下,发现已经可以成功加减色块了。

Vue 3를 사용하여 Fimga 플러그인을 개발하는 과정을 기록합니다.

使用 Vue 3 开发 Figma 插件

通过前面的例子,我们已经清楚 Figma 插件的运行原理。但是用这种“原生”的 jshtml 来编写代码非常低效的。我们完全可以用最新的Web技术来编写代码,只要打包产物包括一个运行在主框架的 js 文件和一个给 iframe 运行的 html 文件即可。我决定尝试使用 Vue 3Figma 데스크톱 앱, 아무 곳이나 마우스 오른쪽 버튼으로 클릭 캔버스에서 플러그인 -> 개발 -> 매니페스트에서 플러그인 가져오기... 메뉴를 열고 이전에 생성된 Manifest.json 파일 경로를 입력하면 플러그인 가져오기에 성공하게 됩니다. 그런 다음

(플러그인 이름)을 마우스 오른쪽 버튼으로 클릭하여 플러그인을 열 수 있습니다.

클릭 버튼을 테스트해 보면 정상적으로 작동합니다. 단지 색상 블록이 아직 페이지에 나타나지 않을 뿐입니다(걱정하지 마세요). 디버깅 콘솔은 플러그인 -> 개발 -> 콘솔 열기를 통해 열 수 있습니다. 우리가 인쇄한 로그를 볼 수 있습니다.

작업 캔버스

앞서 언급했듯이 캔버스 코드는 실행 효율성을 위해 메인 스레드에서만 실행됩니다. 캔버스 콘텐츠, 즉 main.js를 작동합니다. main.js는 캔버스를 작동하는 데 사용되는 일련의 API를 캡슐화하는 최상위 개체 figma를 노출합니다. 자세한 내용은 공식 웹사이트 문서 . figma.createRectangle()을 사용하여 직사각형을 만듭니다. 메인 스레드는 figma.ui.onmessage를 통해 UI 스레드의 이벤트를 수신하여 응답해야 합니다. 수정된 main.js 코드는 다음과 같습니다. Vue + TypeScript 的模板项目。

console.log(&#39;figma plugin code runs!&#39;)

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

const nodes: RectangleNode[] = [];

figma.ui.onmessage = (msg) => {
  
  if (msg.type === "add-block") {
    const rect = figma.createRectangle();
    rect.x = nodes.length * 150;
    rect.fills = [{ type: "SOLID", color: { r: 1, g: 0.5, b: 0 } }];
    figma.currentPage.appendChild(rect);
    nodes.push(rect);
  } else if (msg.type === "sub-block") {
    const rect = nodes.pop();
    if (rect) {
      rect.remove();
    }
  }

  figma.viewport.scrollAndZoomIntoView(nodes);

};
로그인 후 복사

然后通过浏览器打开 http://localhost:3000 就能看到页面。

移植上述demo代码

我们把前面的插件demo移植到 Vue 3 中。 src/App.vue 代码修改如下:

{
  "compilerOptions": {
    // ...
"skipLibCheck": true,
    "typeRoots": [
      "./node_modules/@types",
      "./node_modules/@figma"
    ]
  },
}
로그인 후 복사

我们在 src/worker 目录存放运行在主线程沙箱中的js代码。新建 src/worker/code.ts ,内容如下:

{
  "name": "figma-plugin-vue3",
  "api": "1.0.0",
  "main": "code.js",
  "ui": "index.html",
  "editorType": [
    "figjam",
    "figma"
  ]
}
로그인 후 복사
로그인 후 복사

上述代码中缺少 figma 的 ts 类型声明,所以我们需要安装一下。

npm i -D @figma/plugin-typings

修改 tsconfig.json ,添加 typeRoots ,这样 ts 代码就不会报错了。同时也要加上 "skipLibCheck": true ,解决类型声明冲突问题。

import { defineConfig } from &#39;vite&#39;
import vue from &#39;@vitejs/plugin-vue&#39;
import { resolve } from &#39;path&#39;;

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  build: {
    sourcemap: &#39;inline&#39;,
    rollupOptions: {
      input:{
            main: resolve(__dirname, &#39;index.html&#39;),
            code: resolve(__dirname, &#39;src/worker/code.ts&#39;),
          },
      output: {
        entryFileNames: &#39;[name].js&#39;,
      },
    },
  },
})
로그인 후 복사
로그인 후 복사

修改构建配置

Figma 插件需要的构建产物有:

  • manifest.json 文件作为插件配置

  • index.html 作为UI代码

  • code.js 作为主线程js代码

在 public 目录中添加 manifest.json 文件

public 目录中的文件都会负责到构建产物 dist

import { defineConfig } from &#39;vite&#39;
import vue from &#39;@vitejs/plugin-vue&#39;
import { resolve } from &#39;path&#39;;

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  base: &#39;http://127.0.0.1:3000&#39;,
  build: {
    sourcemap: &#39;inline&#39;,
    rollupOptions: {
      input: {
        main: resolve(__dirname, &#39;index.html&#39;),
        code: resolve(__dirname, &#39;src/worker/code.ts&#39;),
      },
      output: {
        entryFileNames: &#39;[name].js&#39;,
      },
    },
  },
  preview: {
    port: 3000,
  },
})
로그인 후 복사
로그인 후 복사

동시에 index.html의 일부 코드도 수정하여 parent.postMessage 스레드 전송 이벤트: 🎜
npm run watch

# 同时要在另一个终端里启动静态文件服务
npm run preview
로그인 후 복사
로그인 후 복사
🎜 플러그인을 다시 시작하고 다시 시도한 후 색상 블록을 성공적으로 추가하고 뺄 수 있는지 확인하세요. 🎜🎜Vue 3를 사용하여 Fimga 플러그인을 개발하는 과정을 기록합니다.🎜🎜🎜 Vue 3를 사용하여 Figma 플러그인 개발🎜🎜🎜이전 예를 통해 우리는 Figma 플러그인의 작동 방식을 이미 알고 있습니다. 하지만 이 "기본" jshtml을 사용하여 코드를 작성하는 것은 매우 비효율적입니다. 패키지 제품에 메인 프레임에서 실행되는 js 파일과 iframe<에서 실행되는 <code>html이 포함되어 있는 한 최신 웹 기술을 사용하여 코드를 작성할 수 있습니다. /코드> 코드> 파일. 플러그인을 개발하기 위해 Vue 3를 사용해 보기로 결정했습니다. (학습 영상 공유: 🎜vuejs 튜토리얼🎜) 🎜🎜 🎜Vue 3에 대해🎜 너무 많은 소개는 하지 않겠습니다. 아는 사람이라면 누구나 이해할 수 있을 것입니다. 돌아옵니다. 여기서 초점은 사용할 프레임워크가 아니라(vue 2로 변경, 반응 프로세스가 유사함) 구성 도구에 있습니다. 🎜🎜🎜Vite 새 프로젝트 시작🎜🎜🎜🎜Vite🎜은 Vue 작성자가 개발한 차세대 빌드 도구이며 Vue 3에 권장되는 빌드 도구이기도 합니다. 먼저 Vue + TypeScript의 템플릿 프로젝트를 빌드해 보겠습니다. 🎜
const JSDOM = require(&#39;jsdom&#39;);
const fs = require(&#39;fs&#39;);

// 生成 html 文件
function genIndexHtml(sourceHTMLPath, targetHTMLPath) {
  const htmlContent = fs.readFileSync(sourceHTMLPath, &#39;utf-8&#39;);
  const dom = new JSDOM(htmlContent);
  const { document } = dom.window;
  
  const script = document.createElement(&#39;script&#39;);
  script.setAttribute(&#39;type&#39;, &#39;module&#39;);
  script.setAttribute(&#39;src&#39;, &#39;/@vite/client&#39;);
  dom.window.document.head.insertBefore(script, document.head.firstChild);
  
  const base = document.createElement(&#39;base&#39;);
  base.setAttribute(&#39;href&#39;, &#39;http://127.0.0.1:3000/&#39;);
  dom.window.document.head.insertBefore(base, document.head.firstChild);

  const result = dom.serialize();
  fs.writeFileSync(targetHTMLPath, result);
}
로그인 후 복사
로그인 후 복사
🎜그런 다음 브라우저를 통해 http://localhost:3000을 열어 페이지를 확인하세요. 🎜

🎜위 데모 코드 포팅🎜🎜🎜이전 플러그인 데모를 Vue 3으로 포팅했습니다. src/App.vue 코드 수정은 다음과 같습니다. 🎜
const { JSDOM } = require(&#39;jsdom&#39;);
const fs = require(&#39;fs&#39;);
const path = require(&#39;path&#39;);
const vite = require(&#39;vite&#39;);

const rootDir = path.resolve(__dirname, &#39;../&#39;);

function dev() {
  const htmlPath = path.resolve(rootDir, &#39;index.html&#39;);
  const targetHTMLPath = path.resolve(rootDir, &#39;dist/index.html&#39;);
  genIndexHtml(htmlPath, targetHTMLPath);

  buildMainCode();

  startDevServer();
}

// 生成 html 文件
function genIndexHtml(sourceHTMLPath, targetHTMLPath) {
  const htmlContent = fs.readFileSync(sourceHTMLPath, &#39;utf-8&#39;);
  const dom = new JSDOM(htmlContent);
  const {
    document
  } = dom.window;

  const script = document.createElement(&#39;script&#39;);
  script.setAttribute(&#39;type&#39;, &#39;module&#39;);
  script.setAttribute(&#39;src&#39;, &#39;/@vite/client&#39;);
  dom.window.document.head.insertBefore(script, document.head.firstChild);

  const base = document.createElement(&#39;base&#39;);
  base.setAttribute(&#39;href&#39;, &#39;http://127.0.0.1:3000/&#39;);
  dom.window.document.head.insertBefore(base, document.head.firstChild);

  const result = dom.serialize();
  fs.writeFileSync(targetHTMLPath, result);
}

// 构建 code.js 入口
async function buildMainCode() {
  const config = vite.defineConfig({
    configFile: false, // 关闭默认使用的配置文件
    build: {
      emptyOutDir: false, // 不要清空 dist 目录
      lib: { // 使用库模式构建
        entry: path.resolve(rootDir, &#39;src/worker/code.ts&#39;),
        name: &#39;code&#39;,
        formats: [&#39;es&#39;],
        fileName: (format) => `code.js`,
      },
      sourcemap: &#39;inline&#39;,
      watch: {},
    },
  });
  return vite.build(config);
}

// 开启 devServer
async function startDevServer() {
  const config = vite.defineConfig({
    configFile: path.resolve(rootDir, &#39;vite.config.ts&#39;),
    root: rootDir,
    server: {
      hmr: {
        host: &#39;127.0.0.1&#39;, // 必须加上这个,否则 HMR 会报错
      },
      port: 3000,
    },
    build: {
      emptyOutDir: false, // 不要清空 dist 目录
      watch: {}, // 使用 watch 模式
    }
  });
  const server = await vite.createServer(config);
  await server.listen()

  server.printUrls()
}

dev();
로그인 후 복사
로그인 후 복사
🎜메인 스레드 샌드박스에서 실행되는 js 코드를 src/worker 디렉터리에 저장합니다. 다음 내용으로 새 src/worker/code.ts를 만듭니다. 🎜
const { JSDOM } = require(&#39;jsdom&#39;);
const fs = require(&#39;fs&#39;);
const path = require(&#39;path&#39;);
const vite = require(&#39;vite&#39;);
const axios = require(&#39;axios&#39;);

const rootDir = path.resolve(__dirname, &#39;../&#39;);

async function dev() {
  // const htmlPath = path.resolve(rootDir, &#39;index.html&#39;);
  const targetHTMLPath = path.resolve(rootDir, &#39;dist/index.html&#39;);

  await buildMainCode();

  await startDevServer();
  
  // 必须放到 startDevServer 后面执行
  await genIndexHtml(targetHTMLPath);
}

// 生成 html 文件
async function genIndexHtml(/* sourceHTMLPath,*/ targetHTMLPath) {
  const htmlContent = await getHTMLfromDevServer();
  const dom = new JSDOM(htmlContent);
  
  // ...

  const result = dom.serialize();
  fs.writeFileSync(targetHTMLPath, result);
}

// ...

// 通过请求 devServer 获取HTML
async function getHTMLfromDevServer () {
  const rsp = await axios.get(&#39;http://localhost:3000/index.html&#39;);
  return rsp.data;
}

dev();
로그인 후 복사
로그인 후 복사
🎜위 코드에는 figma의 ts 유형 선언이 없으므로 이를 설치해야 합니다. 🎜🎜npm i -D @figma/plugin-typings🎜🎜 tsconfig.json을 수정하고 typeRoots를 추가하여 ts 코드가 보고하지 않도록 합니다. 오류. 또한 유형 선언 충돌을 해결하려면 "skipLibCheck": true를 추가하세요. 🎜rrreee

🎜빌드 구성 수정🎜🎜🎜Figma 플러그인에 필요한 빌드 제품은 다음과 같습니다: 🎜
  • 🎜manifest.json</ code> 파일을 플러그인으로 구성 🎜</li><li>🎜<code>index.html을 UI code🎜
  • 🎜code.js로 구성 메인 스레드로 js 코드🎜

🎜public 디렉터리에 Manifest.json 파일을 추가하세요🎜🎜🎜public 파일 해당 디렉토리의 제품dist 디렉토리 구축을 담당합니다. 🎜
{
  "name": "figma-plugin-vue3",
  "api": "1.0.0",
  "main": "code.js",
  "ui": "index.html",
  "editorType": [
    "figjam",
    "figma"
  ]
}
로그인 후 복사
로그인 후 복사

vite.config.ts 中增加构建入口

默认情况下 vite 会用 index.html 作为构建入口,里面用到的资源会被打包构建。我们还需要一个入口,用来构建主线程 js 代码。

执行 npm i -D @types/node ,安装 Node.js 的类型声明,以便在 ts 中使用 Node.js API。 vite.config.tsbuild.rollupOptions 中增加 input 。默认情况下输出产物会带上文件 hash ,所以也要修改 output 配置:

import { defineConfig } from &#39;vite&#39;
import vue from &#39;@vitejs/plugin-vue&#39;
import { resolve } from &#39;path&#39;;

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  build: {
    sourcemap: &#39;inline&#39;,
    rollupOptions: {
      input:{
            main: resolve(__dirname, &#39;index.html&#39;),
            code: resolve(__dirname, &#39;src/worker/code.ts&#39;),
          },
      output: {
        entryFileNames: &#39;[name].js&#39;,
      },
    },
  },
})
로그인 후 복사
로그인 후 복사

运行构建

执行 npm run builddist 目录会有构建产物。然后我们按照前面的步骤,将 dist 目录添加为 Figma 插件。 Plugins -> Development -> Import plugin from manifest... ,选择 dist/manifest.json 文件路径。

启动插件......怎么插件里一片空白?好在 Figma 里面有 devtools 调试工具,我们打开瞧一瞧。

Vue 3를 사용하여 Fimga 플러그인을 개발하는 과정을 기록합니다.

可以看到,我们的 index.html 已经成功加载,但是 js 代码没加载所以页面空白。js、css 等资源是通过相对路径引用的,而我们的 iframe 中的 src 是一个 base64 格式内容,在寻找 js 资源的时候因为没有域名,所以找不到资源。

解决办法也很简单,我们给资源加上域名,然后本地起一个静态资源服务器就行了。修改 vite.config.ts ,加上 base: &#39;http://127.0.0.1:3000&#39;

import { defineConfig } from &#39;vite&#39;
import vue from &#39;@vitejs/plugin-vue&#39;
import { resolve } from &#39;path&#39;;

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  base: &#39;http://127.0.0.1:3000&#39;,
  build: {
    sourcemap: &#39;inline&#39;,
    rollupOptions: {
      input: {
        main: resolve(__dirname, &#39;index.html&#39;),
        code: resolve(__dirname, &#39;src/worker/code.ts&#39;),
      },
      output: {
        entryFileNames: &#39;[name].js&#39;,
      },
    },
  },
  preview: {
    port: 3000,
  },
})
로그인 후 복사
로그인 후 복사

重新构建代码 npm run build 。然后启动静态资源服务器 npm run preview 。通过浏览器访问 http://localhost:3000/ 可以看到内容。

然后重新打开 Figma 插件看看。果然,插件已经正常了!

Vue 3를 사용하여 Fimga 플러그인을 개발하는 과정을 기록합니다.

Figma 加载插件只需要 index.htmlcode.js ,其他资源都可以通过网络加载。这意味着我们可以将 js、css 资源放在服务端,实现插件的热更?不知道发布插件的时候会不会有限制,这个我还没试过。

开发模式

我们已经能成功通过 Vue 3 来构建 Figma 插件了,但是我不想每次修改代码都要构建一遍,我们需要能够自动构建代码的开发模式。

vite 自动的 dev 模式是启动了一个服务,没有构建产物(而且没有类似webpack里面的 writeToDisk 配置),所以无法使用。

watch 模式

vite 的 build 命令有watch模式,可以监听文件改动然后自动执行 build 。我们只需要修改 package.jsonscripts 里新增 "watch": "vite build --watch"

npm run watch

# 同时要在另一个终端里启动静态文件服务
npm run preview
로그인 후 복사
로그인 후 복사

这种方式虽然修改代码后会自动编译,但是每次还是要关闭插件并重新打开才能看到更新。这样写UI还是太低效了,能不能在插件里实现 HMR (模块热重载)功能呢?

dev 模式

vite dev 的问题在于没有构建产物。 code.js 是运行在 Fimga 主线程沙箱中的,这部分是无法热重载的,所以可以利用 vite build --watch 实现来编译。需要热重载的是 index.html 以及相应的 js 、css 资源。 先来看一下 npm run dev 模式下的 html 资源有什么内容:

Vue 3를 사용하여 Fimga 플러그인을 개발하는 과정을 기록합니다.

理论上来说,我们只需要把这个 html 手动写入到 dist 目录就行,热重载的时候 html 文件不需要修改。直接写入的话会遇到资源是相对路径的问题,所以要么把资源路径都加上域名( http://localhost:3000 ),或者使用 <base>标签。

手动生成 html 文件

对比上面的 html 代码和根目录的 index.html 文件,发现只是增加了一个 <script type="module" src="/@vite/client"></script> 。所以我们可以自己解析 index.html ,然后插入相应这个标签,以及一个 <base> 标签。解析 HTML 我们用 jsdom

const JSDOM = require(&#39;jsdom&#39;);
const fs = require(&#39;fs&#39;);

// 生成 html 文件
function genIndexHtml(sourceHTMLPath, targetHTMLPath) {
  const htmlContent = fs.readFileSync(sourceHTMLPath, &#39;utf-8&#39;);
  const dom = new JSDOM(htmlContent);
  const { document } = dom.window;
  
  const script = document.createElement(&#39;script&#39;);
  script.setAttribute(&#39;type&#39;, &#39;module&#39;);
  script.setAttribute(&#39;src&#39;, &#39;/@vite/client&#39;);
  dom.window.document.head.insertBefore(script, document.head.firstChild);
  
  const base = document.createElement(&#39;base&#39;);
  base.setAttribute(&#39;href&#39;, &#39;http://127.0.0.1:3000/&#39;);
  dom.window.document.head.insertBefore(base, document.head.firstChild);

  const result = dom.serialize();
  fs.writeFileSync(targetHTMLPath, result);
}
로그인 후 복사
로그인 후 복사

同时 vite 提供了 JavaScript API,所以我们可以代码组织起来,写一个 js 脚本来启动开发模式。新建文件 scripts/dev.js ,完整内容如下:

const { JSDOM } = require(&#39;jsdom&#39;);
const fs = require(&#39;fs&#39;);
const path = require(&#39;path&#39;);
const vite = require(&#39;vite&#39;);

const rootDir = path.resolve(__dirname, &#39;../&#39;);

function dev() {
  const htmlPath = path.resolve(rootDir, &#39;index.html&#39;);
  const targetHTMLPath = path.resolve(rootDir, &#39;dist/index.html&#39;);
  genIndexHtml(htmlPath, targetHTMLPath);

  buildMainCode();

  startDevServer();
}

// 生成 html 文件
function genIndexHtml(sourceHTMLPath, targetHTMLPath) {
  const htmlContent = fs.readFileSync(sourceHTMLPath, &#39;utf-8&#39;);
  const dom = new JSDOM(htmlContent);
  const {
    document
  } = dom.window;

  const script = document.createElement(&#39;script&#39;);
  script.setAttribute(&#39;type&#39;, &#39;module&#39;);
  script.setAttribute(&#39;src&#39;, &#39;/@vite/client&#39;);
  dom.window.document.head.insertBefore(script, document.head.firstChild);

  const base = document.createElement(&#39;base&#39;);
  base.setAttribute(&#39;href&#39;, &#39;http://127.0.0.1:3000/&#39;);
  dom.window.document.head.insertBefore(base, document.head.firstChild);

  const result = dom.serialize();
  fs.writeFileSync(targetHTMLPath, result);
}

// 构建 code.js 入口
async function buildMainCode() {
  const config = vite.defineConfig({
    configFile: false, // 关闭默认使用的配置文件
    build: {
      emptyOutDir: false, // 不要清空 dist 目录
      lib: { // 使用库模式构建
        entry: path.resolve(rootDir, &#39;src/worker/code.ts&#39;),
        name: &#39;code&#39;,
        formats: [&#39;es&#39;],
        fileName: (format) => `code.js`,
      },
      sourcemap: &#39;inline&#39;,
      watch: {},
    },
  });
  return vite.build(config);
}

// 开启 devServer
async function startDevServer() {
  const config = vite.defineConfig({
    configFile: path.resolve(rootDir, &#39;vite.config.ts&#39;),
    root: rootDir,
    server: {
      hmr: {
        host: &#39;127.0.0.1&#39;, // 必须加上这个,否则 HMR 会报错
      },
      port: 3000,
    },
    build: {
      emptyOutDir: false, // 不要清空 dist 目录
      watch: {}, // 使用 watch 模式
    }
  });
  const server = await vite.createServer(config);
  await server.listen()

  server.printUrls()
}

dev();
로그인 후 복사
로그인 후 복사

执行 node scripts/dev.js ,然后在 Figma 中重启插件。试试修改一下 Vue 代码,发现插件内容自动更新了!

Vue 3를 사용하여 Fimga 플러그인을 개발하는 과정을 기록합니다.

最后在 package.json 中新建一个修改一下dev的内容为 "dev": "node scripts/dev.js" 就可以了。

通过请求来获取 HTML

前面通过自己生产 index.html 的方式有很大的弊端:万一后续 vite 更新后修改了默认 html 的内容,那我们的脚本也要跟着修改。有没有更健壮的方式呢?我想到可以通过请求 devServer 来获取 html 内容,然后写入本地。话不多说,修改后代码如下:

const { JSDOM } = require(&#39;jsdom&#39;);
const fs = require(&#39;fs&#39;);
const path = require(&#39;path&#39;);
const vite = require(&#39;vite&#39;);
const axios = require(&#39;axios&#39;);

const rootDir = path.resolve(__dirname, &#39;../&#39;);

async function dev() {
  // const htmlPath = path.resolve(rootDir, &#39;index.html&#39;);
  const targetHTMLPath = path.resolve(rootDir, &#39;dist/index.html&#39;);

  await buildMainCode();

  await startDevServer();
  
  // 必须放到 startDevServer 后面执行
  await genIndexHtml(targetHTMLPath);
}

// 生成 html 文件
async function genIndexHtml(/* sourceHTMLPath,*/ targetHTMLPath) {
  const htmlContent = await getHTMLfromDevServer();
  const dom = new JSDOM(htmlContent);
  
  // ...

  const result = dom.serialize();
  fs.writeFileSync(targetHTMLPath, result);
}

// ...

// 通过请求 devServer 获取HTML
async function getHTMLfromDevServer () {
  const rsp = await axios.get(&#39;http://localhost:3000/index.html&#39;);
  return rsp.data;
}

dev();
로그인 후 복사
로그인 후 복사

结语

Figma 基于Web平台的特性使之能成为真正跨平台的设计工具,只要有浏览器就能使用。同时也使得开发插件变得非常简单,非专业人士经过简单的学习也可以上手开发一个插件。而Web社区有数量庞大的开发者,相信 Figma 的插件市场也会越来越繁荣。

本文通过一个例子,详细讲述了使用 Vue 3 开发 Figma 插件的过程,并且完美解决了开发模式下热重载的问题。我将模板代码提交到了 Git 仓库中,需要的同学可以直接下载使用:figma-plugin-vue3

开发 Figma 插件还会遇到一些其他问题,例如如何进行网络请求、本地存储等,有空再继续分享我的实践心得。

本文转载自:https://juejin.cn/post/7084639146915921956

作者:大料园

(学习视频分享:web前端开发

위 내용은 Vue 3를 사용하여 Fimga 플러그인을 개발하는 과정을 기록합니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
vue
원천:juejin.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!