diff --git a/webpack-react-tests/.gitignore b/webpack-react-tests/.gitignore new file mode 100644 index 0000000..04c01ba --- /dev/null +++ b/webpack-react-tests/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +dist/ \ No newline at end of file diff --git a/webpack-react-tests/bun.build.js b/webpack-react-tests/bun.build.js new file mode 100644 index 0000000..1c31652 --- /dev/null +++ b/webpack-react-tests/bun.build.js @@ -0,0 +1,14 @@ +import path from 'path'; + +const results = await Bun.build({ + // entrypoints: ['./src/ui.tsx'], + root: './src' +}); + +for (const result of results.outputs) { + // Can be consumed as blobs + console.log(await result); + console.log(`Writing ${result.kind} ${result.path}`); + // Can be written manually, but you should use `outdir` in this case. + Bun.write(path.join('dist', result.path), result); +} diff --git a/webpack-react-tests/bun.lockb b/webpack-react-tests/bun.lockb new file mode 100755 index 0000000..3e01a1b Binary files /dev/null and b/webpack-react-tests/bun.lockb differ diff --git a/webpack-react-tests/code.spec.ts b/webpack-react-tests/code.spec.ts new file mode 100644 index 0000000..6107fef --- /dev/null +++ b/webpack-react-tests/code.spec.ts @@ -0,0 +1,15 @@ + +// Initialize TestRunner +const testRunner = new TestRunner(); + +// Example usage +describe("Math operations", () => { + describe("Addition", () => { + it("should add two numbers", async () => { + // Test code here + }); + }); +}); + +// Run the tests +testRunner.run(); \ No newline at end of file diff --git a/webpack-react-tests/manifest.json b/webpack-react-tests/manifest.json new file mode 100644 index 0000000..e1377b0 --- /dev/null +++ b/webpack-react-tests/manifest.json @@ -0,0 +1,11 @@ +{ + "name": "Webpack + React Sample", + "id": "webpack-react-sample", + "api": "1.0.0", + "main": "dist/code.js", + "ui": "dist/ui.html", + "editorType": ["figma", "figjam"], + "networkAccess": { + "allowedDomains": ["none"] + } +} diff --git a/webpack-react-tests/package.json b/webpack-react-tests/package.json new file mode 100644 index 0000000..2070384 --- /dev/null +++ b/webpack-react-tests/package.json @@ -0,0 +1,39 @@ +{ + "name": "webpack-react", + "version": "1.0.0", + "description": "", + "main": "code.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "dev": "bun watch", + "build": "bun build", + "wp:dev": "webpack --mode=development --watch", + "wp:build": "webpack --mode=production" + }, + "workspaces": [ + "../../../packages/*" + ], + "author": "Figma", + "license": "MIT", + "devDependencies": { + "@cva/run-figma-run": "workspace:*", + "@figma/plugin-typings": "*", + "@types/node": "^16.7.1", + "@types/react": "^18.2.6", + "@types/react-dom": "^18.2.4", + "bun-types": "^1.0.3", + "css-loader": "^6.2.0", + "html-inline-script-webpack-plugin": "^3.1.0", + "html-webpack-plugin": "^5.3.2", + "style-loader": "^3.2.1", + "ts-loader": "^9.2.5", + "typescript": "^4.3.5", + "url-loader": "^4.1.1", + "webpack": "^5.82.0", + "webpack-cli": "^5.1.1" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + } +} diff --git a/webpack-react-tests/src/code.ts b/webpack-react-tests/src/code.ts new file mode 100644 index 0000000..5255b31 --- /dev/null +++ b/webpack-react-tests/src/code.ts @@ -0,0 +1,20 @@ +figma.showUI(__html__, { themeColors: true, height: 300 }); + +figma.ui.onmessage = (msg) => { + if (msg.type === "create-rectangles") { + const nodes = []; + + 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 } }]; + figma.currentPage.appendChild(rect); + nodes.push(rect); + } + + figma.currentPage.selection = nodes; + figma.viewport.scrollAndZoomIntoView(nodes); + } + + figma.closePlugin(); +}; diff --git a/webpack-react-tests/src/logo.svg b/webpack-react-tests/src/logo.svg new file mode 100644 index 0000000..652ca06 --- /dev/null +++ b/webpack-react-tests/src/logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/webpack-react-tests/src/ui.css b/webpack-react-tests/src/ui.css new file mode 100644 index 0000000..c03cded --- /dev/null +++ b/webpack-react-tests/src/ui.css @@ -0,0 +1,105 @@ +:root { + --color-bg: var(--figma-color-bg); + --color-bg-hover: var(--figma-color-bg-hover); + --color-bg-active: var(--figma-color-bg-pressed); + --color-border: var(--figma-color-border); + --color-border-focus: var(--figma-color-border-selected); + --color-icon: var(--figma-color-icon); + --color-text: var(--figma-color-text); + --color-bg-brand: var(--figma-color-bg-brand); + --color-bg-brand-hover: var(--figma-color-bg-brand-hover); + --color-bg-brand-active: var(--figma-color-bg-brand-pressed); + --color-border-brand: var(--figma-color-border-brand); + --color-border-brand-focus: var(--figma-color-border-selected-strong); + --color-text-brand: var(--figma-color-text-onbrand); +} + +html, +body, +main { + height: 100%; +} + +body, +input, +button { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, + Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; + font-size: 1rem; + text-align: center; +} + +body { + background: var(--color-bg); + color: var(--color-text); + margin: 0; +} + +button { + border-radius: 0.25rem; + background: var(--color-bg); + color: var(--color-text); + cursor: pointer; + border: 1px solid var(--color-border); + padding: 0.5rem 1rem; +} +button:hover { + background-color: var(--color-bg-hover); +} +button:active { + background-color: var(--color-bg-active); +} +button:focus-visible { + border: none; + outline-color: var(--color-border-focus); +} +button.brand { + --color-bg: var(--color-bg-brand); + --color-text: var(--color-text-brand); + --color-bg-hover: var(--color-bg-brand-hover); + --color-bg-active: var(--color-bg-brand-active); + --color-border: transparent; + --color-border-focus: var(--color-border-brand-focus); +} + +input { + background: 1px solid var(--color-bg); + border: 1px solid var(--color-border); + color: 1px solid var(--color-text); + padding: 0.5rem; +} + +input:focus-visible { + border-color: var(--color-border-focus); + outline-color: var(--color-border-focus); +} + +svg { + stroke: var(--color-icon, rgba(0, 0, 0, 0.9)); +} + +main { + align-items: center; + display: flex; + flex-direction: column; + justify-content: center; +} + +section { + align-items: center; + display: flex; + flex-direction: column; + justify-content: center; + margin-bottom: 1rem; +} +section > * + * { + margin-top: 0.5rem; +} +footer > * + * { + margin-left: 0.5rem; +} + +img { + height: auto; + width: 2rem; +} diff --git a/webpack-react-tests/src/ui.html b/webpack-react-tests/src/ui.html new file mode 100644 index 0000000..5e518c7 --- /dev/null +++ b/webpack-react-tests/src/ui.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/webpack-react-tests/src/ui.tsx b/webpack-react-tests/src/ui.tsx new file mode 100644 index 0000000..3c49fdd --- /dev/null +++ b/webpack-react-tests/src/ui.tsx @@ -0,0 +1,42 @@ +import * as React from "react"; +import * as ReactDOM from "react-dom/client"; +import "./ui.css"; + +declare function require(path: string): any; + +function App() { + const inputRef = React.useRef(null); + + const onCreate = () => { + const count = Number(inputRef.current?.value || 0); + parent.postMessage( + { pluginMessage: { type: "create-rectangles", count } }, + "*" + ); + }; + + const onCancel = () => { + parent.postMessage({ pluginMessage: { type: "cancel" } }, "*"); + }; + + return ( +
+
+ +

Rectangle Creator

+
+
+ + +
+
+ + +
+
+ ); +} + +ReactDOM.createRoot(document.getElementById("react-page")).render(); diff --git a/webpack-react-tests/tsconfig.json b/webpack-react-tests/tsconfig.json new file mode 100644 index 0000000..6a6f524 --- /dev/null +++ b/webpack-react-tests/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es6", + "jsx": "react", + "typeRoots": [ + "./node_modules/@types", + "./node_modules/@figma" + ] + }, + "include": ["src/**/*.ts", "src/**/*.tsx"] +} diff --git a/webpack-react-tests/webpack.config.js b/webpack-react-tests/webpack.config.js new file mode 100644 index 0000000..1ef7fb9 --- /dev/null +++ b/webpack-react-tests/webpack.config.js @@ -0,0 +1,71 @@ +const HtmlInlineScriptPlugin = require('html-inline-script-webpack-plugin'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); + +const path = require('path'); +const webpack = require('webpack'); + +module.exports = (env, argv) => ({ + mode: argv.mode === 'production' ? 'production' : 'development', + + // This is necessary because Figma's 'eval' works differently than normal eval + devtool: argv.mode === 'production' ? false : 'inline-source-map', + + entry: { + ui: './src/ui.tsx', // The entry point for your UI code + code: './src/code.ts', // The entry point for your plugin code + }, + + module: { + rules: [ + // Converts TypeScript code to JavaScript + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/, + }, + + // Enables including CSS by doing "import './file.css'" in your TypeScript code + { + test: /\.css$/, + use: ['style-loader', 'css-loader'], + }, + // Allows you to use "<%= require('./file.svg') %>" in your HTML code to get a data URI + // { test: /\.(png|jpg|gif|webp|svg|zip)$/, loader: [{ loader: 'url-loader' }] } + { + test: /\.svg/, + type: 'asset/inline', + }, + ], + }, + + // Webpack tries these extensions for you if you omit the extension like "import './file'" + resolve: { extensions: ['.tsx', '.ts', '.jsx', '.js'] }, + + output: { + filename: (pathData) => { + return pathData.chunk.name === 'code' + ? 'code.js' + : '[name].[contenthash].js'; + }, + path: path.resolve(__dirname, 'dist'), // Compile into a folder called "dist" + // Clean the output directory before emit. + clean: true, + }, + + // Tells Webpack to generate "ui.html" and to inline "ui.ts" into it + plugins: [ + new webpack.DefinePlugin({ + global: {}, // Fix missing symbol error when running in developer VM + }), + new HtmlWebpackPlugin({ + inject: 'body', + template: './src/ui.html', + filename: 'ui.html', + chunks: ['ui'], + }), + new HtmlInlineScriptPlugin({ + htmlMatchPattern: [/ui.html/], + scriptMatchPattern: [/.js$/], + }), + ], +});