create-figma-app
Advanced tools
Comparing version 0.0.3 to 0.0.4
# create-figma-app | ||
## 0.0.4 | ||
### Patch Changes | ||
- refactor: refactor postmessage bridge and fix some bugs | ||
## 0.0.3 | ||
@@ -4,0 +10,0 @@ |
{ | ||
"name": "create-figma-app", | ||
"version": "0.0.3", | ||
"version": "0.0.4", | ||
"type": "module", | ||
@@ -21,7 +21,2 @@ "description": "create plugin app for figma", | ||
}, | ||
"scripts": { | ||
"dev": "unbuild --stub", | ||
"build": "unbuild", | ||
"prepublishOnly": "npm run build" | ||
}, | ||
"files": [ | ||
@@ -49,3 +44,7 @@ "index.js", | ||
"registry": "https://registry.npmjs.org/" | ||
}, | ||
"scripts": { | ||
"dev": "unbuild --stub", | ||
"build": "unbuild" | ||
} | ||
} | ||
} |
# create-figma-app [![NPM Version](https://img.shields.io/npm/v/create-figma-app)](https://www.npmjs.com/package/create-figma-app) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](./CONTRIBUTING.md) | ||
Quick create a [plugin app](https://www.figma.com/plugin-docs/) for figma | ||
Quick create a [Figma plugin app](https://www.figma.com/plugin-docs/). | ||
@@ -9,3 +9,4 @@ ## Usage | ||
```bash | ||
# npm | ||
# npm | ||
# my-figma-app is project dir name | ||
npm create figma-app@latest my-figma-app | ||
@@ -19,10 +20,24 @@ | ||
- `react-ts`:react、typescript、rspack | ||
you can use `.` for the project name or input a project by | ||
### react-ts | ||
```bash | ||
npm create figma-app@latest <projectName> | ||
``` | ||
- using react、tailwind、typescript、rspack | ||
It has the following advantages: | ||
- Simplify postmessage communication between ui and sandbox | ||
- Sandbox is published together with ui, no need to publish sandbox in Figma | ||
- Good TypeScript type definition, ui and sandbox know each other's function interface | ||
- Extremely small sandbox code size, only contains sandbox code | ||
See [More about this template](./template-react-ts/README.md) | ||
![](https://figma-plugin-template-1307850796.cos.ap-beijing.myqcloud.com/plugin-screenshot.png) | ||
## Figma Plugins using this template | ||
- [Quick Replace Font](https://www.figma.com/community/plugin/1241949869279607046/quick-replace-font): Replace fonts with one click. used by 3.5k users. | ||
![](https://site-1307850796.cos.ap-beijing.myqcloud.com/quick-replace-font.png) | ||
## Contribution | ||
@@ -29,0 +44,0 @@ |
@@ -14,2 +14,4 @@ { | ||
"build:sandbox": "esbuild ./src/sandbox/sandbox/index.ts --bundle --outfile=src/react/rpc/sandbox.js --target=chrome58", | ||
"build:rsdoctor": "cross-env NODE_ENV=production RSDOCTOR=true rspack build", | ||
"build:analyze": "cross-env NODE_ENV=production rspack build --analyze", | ||
"lint": "eslint --ext .ts,.tsx --ignore-pattern node_modules .", | ||
@@ -19,2 +21,3 @@ "lint:fix": "eslint --ext .ts,.tsx --ignore-pattern node_modules --fix ." | ||
"dependencies": { | ||
"jsonrpc-over-postmessage": "^0.0.2", | ||
"react": "^18.2.0", | ||
@@ -27,2 +30,3 @@ "react-dom": "^18.2.0" | ||
"@figma/plugin-typings": "*", | ||
"@rsdoctor/rspack-plugin": "^0.4.8", | ||
"@rspack/cli": "1.0.2", | ||
@@ -29,0 +33,0 @@ "@rspack/core": "1.0.2", |
# figma-plugin-template | ||
A figma template using React, Typescript, JSON RPC. | ||
A figma template using React, Tailwind, Typescript, JSON RPC. | ||
<img width="1728" alt="image" src="https://github.com/user-attachments/assets/07858825-23be-46e7-b723-b0da8cdf0cc9"> | ||
- Simplify postmessage communication between ui and sandbox | ||
- Sandbox is published together with ui, no need to publish sandbox in Figma | ||
- Good TypeScript type definition, ui and sandbox know each other's function interface | ||
- Extremely small sandbox code size, only contains sandbox code | ||
## Install | ||
![](https://figma-plugin-template-1307850796.cos.ap-beijing.myqcloud.com/plugin-screenshot.png) | ||
## Development | ||
```bash | ||
pnpm install | ||
npm run dev | ||
``` | ||
## Development | ||
### Code Structure | ||
```bash | ||
npm run dev | ||
``` | ||
├── react // react project | ||
│ ├── App.tsx | ||
│ ├── index.css | ||
│ ├── index.html | ||
│ ├── index.tsx | ||
│ ├── rpc | ||
| | ├── index.ts // create postmessage handler in ui | ||
| | ├── handlers.ts // ui handlers for sandbox event | ||
| | └── sandbox.js // [auto generated, no need to modify this file] ui will get the sandbox code (a url, same origin with ui), and then postmessage it to the sandbox and execute it | ||
│ └── utils | ||
└── sandbox | ||
| ├── code // sandbox init code. When the plugin is first published in Figma, this file (`code.js` in dist) needs to be published | ||
| └── sandbox // sandbox core code, will be bundled to `react/rpc/sandbox.js` | ||
| ├── index.ts // create postmessage handler in sandbox | ||
| └── handlers.ts // sandbox handlers for ui event | ||
``` | ||
- we use [rspack](https://rspack.dev/) to bundle web project | ||
- we use [esbuild](https://esbuild.github.io/) to bundle sandbox code | ||
@@ -28,2 +46,4 @@ ## Build | ||
![](https://figma-plugin-template-1307850796.cos.ap-beijing.myqcloud.com/build.png) | ||
Here are the bundles: | ||
@@ -46,73 +66,32 @@ | ||
<img width="1728" alt="image" src="https://github.com/user-attachments/assets/99f722e8-451d-4e24-9828-c9abcedf6cbb"> | ||
![](https://figma-plugin-template-1307850796.cos.ap-beijing.myqcloud.com/replace-your-cdn.png) | ||
## One more thing | ||
Your entrance page will be here, check it. | ||
We use JSON rpc with typings, so that we can know each interface. | ||
![](https://figma-plugin-template-1307850796.cos.ap-beijing.myqcloud.com/check-cdn.png) | ||
- before | ||
```typescript | ||
// plugin ui postmessage to sandbox code | ||
parent.postMessage({ pluginMessage: { type: 'create-rectangles', count } }, '*') | ||
``` | ||
> If you need to customize the build process, [rspack doc](https://rspack.dev/) and [esbuild doc](https://esbuild.github.io/) will be helpful. | ||
```typescript | ||
// sandbox code listen and do sothing | ||
figma.ui.onmessage = (msg: { type: string, count: number }) => { | ||
// One way of distinguishing between different types of messages sent from | ||
// your HTML page is to use an object with a "type" property like this. | ||
if (msg.type === 'create-rectangles') { | ||
const nodes: SceneNode[] = []; | ||
for (let i = 0; i < msg.count; i++) { | ||
const rect = figma.createRectangle(); | ||
rect.x = i * 150; | ||
rect.fills = [{ type: 'SOLID', color: { r: 1, g: 0.5, b: 0 } }] as any; | ||
figma.currentPage.appendChild(rect); | ||
nodes.push(rect); | ||
} | ||
figma.currentPage.selection = nodes; | ||
figma.viewport.scrollAndZoomIntoView(nodes); | ||
} | ||
## Publish | ||
// Make sure to close the plugin when you're done. Otherwise the plugin will | ||
// keep running, which shows the cancel button at the bottom of the screen. | ||
figma.closePlugin(); | ||
}; | ||
``` | ||
- First publish: All you need is drop `dist/web` to your cdn, and check the effect in the development environment. | ||
- after | ||
- Subsequent publish: like first time, or you can automate this process through CI, depending on the code management tool or pipeline you use (Github, Gitlab, Jenkins, and so on) | ||
```typescript | ||
// plugin ui call codeApi by JSON rpc | ||
codeApi.createRectangles(count); | ||
``` | ||
## Performance | ||
```typescript | ||
// sandbox code declare handler | ||
function createRectangles(count: number) { | ||
const nodes: SceneNode[] = []; | ||
for (let i = 0; i < count; i++) { | ||
const rect = figma.createRectangle(); | ||
rect.x = i * 150; | ||
rect.fills = [{ type: 'SOLID', color: { r: 1, g: 0.5, b: 0 } }] as any; | ||
figma.currentPage.appendChild(rect); | ||
nodes.push(rect); | ||
} | ||
figma.currentPage.selection = nodes; | ||
figma.viewport.scrollAndZoomIntoView(nodes); | ||
} | ||
// other handlers | ||
You can choose any one of the following, or use a combination of | ||
const handlers = { | ||
createRectangles, | ||
}; | ||
- [Rsdoctor](https://rspack.dev/zh/guide/optimization/use-rsdoctor): `npm run build:rsdoctor` | ||
export default handlers; | ||
``` | ||
![](https://figma-plugin-template-1307850796.cos.ap-beijing.myqcloud.com/rsdoctor.png) | ||
And we don't have to bundle plugin api's handlers to code api or vice versa using [Proxy](./src/rpc/proxy.ts)! | ||
- [webpack-bundle-analyzer](https://rspack.dev/zh/guide/optimization/analysis#webpack-bundle-analyzer): `npm run build:analyze` | ||
![](https://figma-plugin-template-1307850796.cos.ap-beijing.myqcloud.com/webpack-bundle-analyzer.png) | ||
## Thanks | ||
- JSON rpc work is inspired by [figma-JSONRPC](https://github.com/Lona/figma-jsonrpc/tree/master) |
@@ -0,1 +1,2 @@ | ||
import { RsdoctorRspackPlugin } from '@rsdoctor/rspack-plugin'; | ||
import { defineConfig } from '@rspack/cli'; | ||
@@ -8,7 +9,6 @@ import { rspack } from '@rspack/core'; | ||
// 需要换成自己的 CDN 地址 | ||
const CDN_ADDRESS = | ||
'https://pluin-1307850796.cos.ap-nanjing.myqcloud.com/template/'; | ||
const CDN_ADDRESS = 'https://YOUR_CDN_ADDRESS/'; | ||
const templatePath = isDev | ||
? `http://localhost:3000/index.html` | ||
: path.join(CDN_ADDRESS, 'index.html'); | ||
: `${CDN_ADDRESS}index.html`; | ||
@@ -23,3 +23,3 @@ export default defineConfig({ | ||
path: path.resolve(__dirname, 'dist/web'), | ||
publicPath: isDev ? '/' : CDN_ADDRESS, | ||
publicPath: isDev ? 'http://localhost:3000/' : CDN_ADDRESS, | ||
filename: '[name].[contenthash].js', | ||
@@ -112,2 +112,8 @@ cssFilename: '[name].[contenthash].css', | ||
new rspack.ProgressPlugin({}), | ||
process.env.RSDOCTOR && | ||
new RsdoctorRspackPlugin({ | ||
supports: { | ||
generateTileGraph: true, | ||
}, | ||
}), | ||
new rspack.HtmlRspackPlugin({ | ||
@@ -114,0 +120,0 @@ template: './src/react/index.html', |
@@ -0,4 +1,6 @@ | ||
import { createRpcApi } from 'jsonrpc-over-postmessage'; | ||
import { SandboxHandlers } from '@sandbox/sandbox'; | ||
import manifest from '../../../manifest.json'; | ||
import { createRpcApi } from '../../rpc'; | ||
import { getSandboxContent } from '../utils'; | ||
// @ts-ignore ignore | ||
@@ -31,7 +33,7 @@ import sandbox from './sandbox.js'; | ||
// 注册 sandbox.js | ||
sandboxApi | ||
.executeScriptURL(sandbox) | ||
.then(({ message }) => console.log(message)) | ||
.catch(({ message }) => console.error(message)); | ||
export {}; | ||
export const registerSandboxApi = async () => { | ||
const sandboxContent = await getSandboxContent(sandbox); | ||
if (sandboxContent) { | ||
return sandboxApi.executeScript(sandboxContent); | ||
} | ||
}; |
@@ -22,10 +22,7 @@ "use strict"; | ||
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); | ||
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); | ||
// src/rpc/errors.ts | ||
// node_modules/.pnpm/jsonrpc-over-postmessage@0.0.2/node_modules/jsonrpc-over-postmessage/dist/errors.js | ||
var InvalidRequest = class extends Error { | ||
constructor(data) { | ||
super("Invalid Request"); | ||
__publicField(this, "data"); | ||
__publicField(this, "statusCode"); | ||
this.data = data; | ||
@@ -38,4 +35,2 @@ this.statusCode = -32600; | ||
super("Method not found"); | ||
__publicField(this, "data"); | ||
__publicField(this, "statusCode"); | ||
this.data = data; | ||
@@ -46,3 +41,3 @@ this.statusCode = -32601; | ||
// src/rpc/rpc.ts | ||
// node_modules/.pnpm/jsonrpc-over-postmessage@0.0.2/node_modules/jsonrpc-over-postmessage/dist/rpc.js | ||
var rpcIndex = 0; | ||
@@ -56,3 +51,3 @@ var pending = {}; | ||
} else { | ||
target == null ? void 0 : target.postMessage(raw, targetOrigin); | ||
target === null || target === void 0 ? void 0 : target.postMessage(raw, targetOrigin); | ||
} | ||
@@ -145,6 +140,3 @@ } | ||
if (!callback) { | ||
sendError( | ||
id, | ||
new InvalidRequest("Missing callback for " + json.id) | ||
); | ||
sendError(id, new InvalidRequest("Missing callback for " + json.id)); | ||
return; | ||
@@ -178,3 +170,3 @@ } | ||
// src/rpc/config.ts | ||
// node_modules/.pnpm/jsonrpc-over-postmessage@0.0.2/node_modules/jsonrpc-over-postmessage/dist/config.js | ||
var config = { | ||
@@ -194,3 +186,3 @@ target: null, | ||
// src/rpc/proxy.ts | ||
// node_modules/.pnpm/jsonrpc-over-postmessage@0.0.2/node_modules/jsonrpc-over-postmessage/dist/proxy.js | ||
function createApiProxy() { | ||
@@ -212,3 +204,3 @@ const target = { | ||
// src/rpc/index.ts | ||
// node_modules/.pnpm/jsonrpc-over-postmessage@0.0.2/node_modules/jsonrpc-over-postmessage/dist/index.js | ||
var createRpcApi = (options) => { | ||
@@ -270,7 +262,7 @@ initConfig(options); | ||
// src/sandbox/sandbox/index.ts | ||
createRpcApi({ | ||
var uiApi = createRpcApi({ | ||
postmessage: (message) => figma.ui.postMessage(message, { origin: "*" }), | ||
onmessage: (handler) => { | ||
figma.ui.onmessage = (message) => { | ||
console.log("sandbox message", message); | ||
console.log("code message", message); | ||
handler(message); | ||
@@ -282,4 +274,11 @@ }; | ||
figma.on("selectionchange", () => { | ||
console.log("selectionchange"); | ||
const _selection = figma.currentPage.selection; | ||
const selection = _selection.map((item) => ({ | ||
id: item.id, | ||
name: item.name, | ||
type: item.type | ||
})); | ||
console.log("selectionchange", selection); | ||
uiApi.handleSelectionChange(selection); | ||
}); | ||
})(); |
@@ -1,2 +0,3 @@ | ||
import { createRpcApi } from '@rpc'; | ||
import { createRpcApi } from 'jsonrpc-over-postmessage'; | ||
import { codeHandlers } from './handlers'; | ||
@@ -3,0 +4,0 @@ |
@@ -1,2 +0,4 @@ | ||
import { createRpcApi } from '../../rpc/index'; | ||
import { createRpcApi } from 'jsonrpc-over-postmessage'; | ||
import { CodeHandlers } from '../../react/rpc/handlers'; | ||
import handlers from './handlers'; | ||
@@ -10,4 +12,3 @@ | ||
*/ | ||
createRpcApi({ | ||
const uiApi = createRpcApi<CodeHandlers>({ | ||
postmessage: (message: unknown) => | ||
@@ -17,3 +18,3 @@ figma.ui.postMessage(message, { origin: '*' }), | ||
figma.ui.onmessage = (message) => { | ||
console.log('sandbox message', message); | ||
console.log('code message', message); | ||
handler(message); | ||
@@ -25,7 +26,17 @@ }; | ||
// 编写全局的监听函数也是可以的 | ||
/** | ||
* 监听 figma 选中元素的变化 | ||
* 调用 uiApi | ||
*/ | ||
figma.on('selectionchange', () => { | ||
console.log('selectionchange'); | ||
const _selection = figma.currentPage.selection; | ||
const selection = _selection.map((item) => ({ | ||
id: item.id, | ||
name: item.name, | ||
type: item.type, | ||
})); | ||
console.log('selectionchange', selection); | ||
uiApi.handleSelectionChange(selection); | ||
}); | ||
export type { SandboxHandlers } from './handlers'; |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
47
65303
28
1029