feat: add TypeScript support

This commit is contained in:
Juanfran
2025-11-05 17:09:45 +01:00
parent dde0fddd6f
commit a08c2ca46e
17 changed files with 1031 additions and 18 deletions

View File

@@ -30,13 +30,18 @@ tmux select-window -t penpot:1
tmux send-keys -t penpot 'cd penpot/frontend' enter C-l tmux send-keys -t penpot 'cd penpot/frontend' enter C-l
tmux send-keys -t penpot 'yarn run watch:app' enter tmux send-keys -t penpot 'yarn run watch:app' enter
tmux new-window -t penpot:2 -n 'frontend storybook' tmux new-window -t penpot:2 -n 'frontend ts'
tmux select-window -t penpot:2 tmux select-window -t penpot:2
tmux send-keys -t penpot 'cd penpot/frontend' enter C-l tmux send-keys -t penpot 'cd penpot/frontend' enter C-l
tmux send-keys -t penpot 'yarn watch:ts' enter
tmux new-window -t penpot:3 -n 'frontend storybook'
tmux select-window -t penpot:3
tmux send-keys -t penpot 'cd penpot/frontend' enter C-l
tmux send-keys -t penpot 'yarn run watch:storybook' enter tmux send-keys -t penpot 'yarn run watch:storybook' enter
tmux new-window -t penpot:3 -n 'exporter' tmux new-window -t penpot:4 -n 'exporter'
tmux select-window -t penpot:3 tmux select-window -t penpot:4
tmux send-keys -t penpot 'cd penpot/exporter' enter C-l tmux send-keys -t penpot 'cd penpot/exporter' enter C-l
tmux send-keys -t penpot 'rm -f target/app.js*' enter C-l tmux send-keys -t penpot 'rm -f target/app.js*' enter C-l
tmux send-keys -t penpot 'yarn run watch' enter tmux send-keys -t penpot 'yarn run watch' enter
@@ -45,8 +50,8 @@ tmux split-window -v
tmux send-keys -t penpot 'cd penpot/exporter' enter C-l tmux send-keys -t penpot 'cd penpot/exporter' enter C-l
tmux send-keys -t penpot './scripts/wait-and-start.sh' enter tmux send-keys -t penpot './scripts/wait-and-start.sh' enter
tmux new-window -t penpot:4 -n 'backend' tmux new-window -t penpot:5 -n 'backend'
tmux select-window -t penpot:4 tmux select-window -t penpot:5
tmux send-keys -t penpot 'cd penpot/backend' enter C-l tmux send-keys -t penpot 'cd penpot/backend' enter C-l
tmux send-keys -t penpot './scripts/start-dev' enter tmux send-keys -t penpot './scripts/start-dev' enter

2
frontend/.gitignore vendored
View File

@@ -11,4 +11,4 @@ node_modules/
/blob-report/ /blob-report/
/playwright/.cache/ /playwright/.cache/
/playwright/**/visual-specs/**/*.png /playwright/**/visual-specs/**/*.png
/ts/dist/

View File

@@ -1,6 +1,6 @@
/** @type { import('@storybook/react-vite').StorybookConfig } */ /** @type { import('@storybook/react-vite').StorybookConfig } */
const config = { const config = {
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"], stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)", "../ts/src/components/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
staticDirs: ["../resources/public"], staticDirs: ["../resources/public"],
addons: [ addons: [
"@storybook/addon-themes", "@storybook/addon-themes",

23
frontend/eslint.config.js Normal file
View File

@@ -0,0 +1,23 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import { defineConfig, globalIgnores } from 'eslint/config'
export default defineConfig([
globalIgnores(['ts/dist']),
{
files: ['ts/src/**/*.{ts,tsx}'],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs['recommended-latest'],
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
},
])

View File

@@ -17,7 +17,8 @@
"@zip.js/zip.js@npm:^2.7.44": "patch:@zip.js/zip.js@npm%3A2.7.60#~/.yarn/patches/@zip.js-zip.js-npm-2.7.60-b6b814410b.patch", "@zip.js/zip.js@npm:^2.7.44": "patch:@zip.js/zip.js@npm%3A2.7.60#~/.yarn/patches/@zip.js-zip.js-npm-2.7.60-b6b814410b.patch",
"@vitejs/plugin-react": "^4.2.0", "@vitejs/plugin-react": "^4.2.0",
"playwright": "1.52.0", "playwright": "1.52.0",
"playwright-core": "1.52.0" "playwright-core": "1.52.0",
"globals": "^16.5.0"
}, },
"scripts": { "scripts": {
"build:app:assets": "node ./scripts/build-app-assets.js", "build:app:assets": "node ./scripts/build-app-assets.js",
@@ -32,8 +33,9 @@
"e2e:server": "node ./scripts/e2e-server.js", "e2e:server": "node ./scripts/e2e-server.js",
"fmt:clj": "cljfmt fix --parallel=true src/ test/", "fmt:clj": "cljfmt fix --parallel=true src/ test/",
"fmt:clj:check": "cljfmt check --parallel=false src/ test/", "fmt:clj:check": "cljfmt check --parallel=false src/ test/",
"fmt:js": "yarn run prettier -c src/**/*.stories.jsx -c playwright/**/*.js -c scripts/**/*.js -w", "fmt:js": "yarn run prettier -c src/**/*.stories.jsx -c playwright/**/*.js -c scripts/**/*.js -c ts/src/**/*.ts -w",
"fmt:js:check": "yarn run prettier -c src/**/*.stories.jsx -c playwright/**/*.js -c scripts/**/*.js", "fmt:js:check": "yarn run prettier -c src/**/*.stories.jsx -c playwright/**/*.js -c scripts/**/*.js -c ts/src/**/*.ts -c --check",
"lint:ts": "eslint ts/",
"lint:clj": "clj-kondo --parallel --lint src/", "lint:clj": "clj-kondo --parallel --lint src/",
"lint:scss": "yarn run prettier -c resources/styles -c src/**/*.scss", "lint:scss": "yarn run prettier -c resources/styles -c src/**/*.scss",
"lint:scss:fix": "yarn run prettier -c resources/styles -c src/**/*.scss -w", "lint:scss:fix": "yarn run prettier -c resources/styles -c src/**/*.scss -w",
@@ -50,6 +52,7 @@
"watch:app": "yarn run clear:shadow-cache && concurrently \"yarn run watch:app:main\" \"yarn run watch:app:libs\"", "watch:app": "yarn run clear:shadow-cache && concurrently \"yarn run watch:app:main\" \"yarn run watch:app:libs\"",
"watch": "yarn run watch:app:assets", "watch": "yarn run watch:app:assets",
"watch:storybook": "yarn run build:storybook:assets && concurrently \"storybook dev -p 6006 --no-open\" \"yarn run watch:storybook:assets\"", "watch:storybook": "yarn run build:storybook:assets && concurrently \"storybook dev -p 6006 --no-open\" \"yarn run watch:storybook:assets\"",
"watch:ts": "vite build --watch",
"watch:storybook:assets": "node ./scripts/watch-storybook.js" "watch:storybook:assets": "node ./scripts/watch-storybook.js"
}, },
"devDependencies": { "devDependencies": {
@@ -59,11 +62,16 @@
"@storybook/addon-vitest": "10.0.4", "@storybook/addon-vitest": "10.0.4",
"@storybook/react-vite": "10.0.4", "@storybook/react-vite": "10.0.4",
"@types/node": "^22.15.21", "@types/node": "^22.15.21",
"@types/react": "^19.1.16",
"@types/react-dom": "^19.1.9",
"@vitest/browser": "3.2.4", "@vitest/browser": "3.2.4",
"@vitest/coverage-v8": "3.2.4", "@vitest/coverage-v8": "3.2.4",
"autoprefixer": "^10.4.21", "autoprefixer": "^10.4.21",
"concurrently": "^9.2.1", "concurrently": "^9.2.1",
"esbuild": "^0.25.9", "esbuild": "^0.25.9",
"eslint": "^9.36.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.22",
"express": "^5.1.0", "express": "^5.1.0",
"fancy-log": "^2.0.0", "fancy-log": "^2.0.0",
"getopts": "^2.3.0", "getopts": "^2.3.0",
@@ -95,6 +103,7 @@
"storybook": "10.0.4", "storybook": "10.0.4",
"svg-sprite": "^2.0.4", "svg-sprite": "^2.0.4",
"typescript": "^5.9.2", "typescript": "^5.9.2",
"typescript-eslint": "^8.45.0",
"vite": "^6.3.5", "vite": "^6.3.5",
"vitest": "^3.2.0", "vitest": "^3.2.0",
"wasm-pack": "^0.13.1", "wasm-pack": "^0.13.1",
@@ -108,7 +117,9 @@
"@penpot/plugins-runtime": "1.3.2", "@penpot/plugins-runtime": "1.3.2",
"@penpot/svgo": "penpot/svgo#v3.1", "@penpot/svgo": "penpot/svgo#v3.1",
"@penpot/text-editor": "portal:./text-editor", "@penpot/text-editor": "portal:./text-editor",
"@penpot/ts": "portal:./ts",
"@tokens-studio/sd-transforms": "1.2.11", "@tokens-studio/sd-transforms": "1.2.11",
"@vitejs/plugin-react": "4.2.0",
"@zip.js/zip.js": "patch:@zip.js/zip.js@npm%3A2.7.60#~/.yarn/patches/@zip.js-zip.js-npm-2.7.60-b6b814410b.patch", "@zip.js/zip.js": "patch:@zip.js/zip.js@npm%3A2.7.60#~/.yarn/patches/@zip.js-zip.js-npm-2.7.60-b6b814410b.patch",
"compression": "^1.8.1", "compression": "^1.8.1",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",

View File

@@ -18,6 +18,7 @@
<meta name="twitter:creator" content="@penpotapp"> <meta name="twitter:creator" content="@penpotapp">
<meta name="theme-color" content="#FFFFFF" media="(prefers-color-scheme: light)"> <meta name="theme-color" content="#FFFFFF" media="(prefers-color-scheme: light)">
<link id="theme" href="css/main.css?version={{& version}}" rel="stylesheet" type="text/css" /> <link id="theme" href="css/main.css?version={{& version}}" rel="stylesheet" type="text/css" />
<link href="css/ts-style.css?ts={{& ts}}" rel="stylesheet" type="text/css" />
{{#isDebug}} {{#isDebug}}
<link href="css/debug.css?version={{& version}}" rel="stylesheet" type="text/css" /> <link href="css/debug.css?version={{& version}}" rel="stylesheet" type="text/css" />
{{/isDebug}} {{/isDebug}}

View File

@@ -6,6 +6,7 @@
(ns app.main (ns app.main
(:require (:require
["@penpot/ts" :refer [setTranslation]]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.logging :as log] [app.common.logging :as log]
[app.common.types.objects-map] [app.common.types.objects-map]
@@ -38,6 +39,8 @@
(log/setup! {:app :info}) (log/setup! {:app :info})
(log/set-level! :debug) (log/set-level! :debug)
(setTranslation i18n/tr)
(when (= :browser cf/target) (when (= :browser cf/target)
(log/inf :version (:full cf/version) (log/inf :version (:full cf/version)
:asserts *assert* :asserts *assert*

View File

@@ -7,6 +7,7 @@
(ns app.main.ui.dashboard.projects (ns app.main.ui.dashboard.projects
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
["@penpot/ts" :refer [TestTsxComponent]]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.time :as ct] [app.common.time :as ct]
[app.main.data.common :as dcm] [app.main.data.common :as dcm]
@@ -50,8 +51,11 @@
::mf/props :obj ::mf/props :obj
::mf/private true} ::mf/private true}
[{:keys [can-edit]}] [{:keys [can-edit]}]
;; (js/console.log "xxxx" TestTsxComponent)
(let [on-click (mf/use-fn #(st/emit! (dd/create-project)))] (let [on-click (mf/use-fn #(st/emit! (dd/create-project)))]
[:header {:class (stl/css :dashboard-header) :data-testid "dashboard-header"} [:header {:class (stl/css :dashboard-header) :data-testid "dashboard-header"}
[:div
[:> TestTsxComponent]]
[:div#dashboard-projects-title {:class (stl/css :dashboard-title)} [:div#dashboard-projects-title {:class (stl/css :dashboard-title)}
[:h1 (tr "dashboard.projects-title")]] [:h1 (tr "dashboard.projects-title")]]
(when can-edit (when can-edit

8
frontend/ts/package.json Normal file
View File

@@ -0,0 +1,8 @@
{
"name": "@penpot/ts",
"version": "1.0.0",
"description": "",
"main": "dist/index.js",
"type": "module",
"packageManager": "yarn@4.3.1"
}

View File

@@ -0,0 +1,3 @@
.title {
color: green;
}

View File

@@ -0,0 +1,16 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) KALEIDOS INC
import {TestTsxComponent } from "./test-tsx";
export default {
title: "TestTSX",
component: TestTsxComponent,
argTypes: {},
parameters: {},
};
export const Default = {};

View File

@@ -0,0 +1,11 @@
import styles from './test-tsx.module.css';
import { translate } from '../penpot-bridge';
export const TestTsxComponent = () => {
return (
<div>
<h2 className={styles.title}>{translate("labels.delete")} xx</h2>
</div>
);
};

View File

@@ -0,0 +1,4 @@
/* eslint-disable */
export { TestTsxComponent } from "./components/test-tsx";
export { setTranslation } from "./penpot-bridge";

View File

@@ -0,0 +1,7 @@
export let translate = (key: string) => {
return key;
};
export function setTranslation(translations: (key: string) => string) {
translate = translations;
}

28
frontend/tsconfig.json Normal file
View File

@@ -0,0 +1,28 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2022",
"useDefineForClassFields": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"types": ["vite/client"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["ts/src"]
}

View File

@@ -1,8 +1,25 @@
/// <reference types="vitest/config" /> /// <reference types="vitest/config" />
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import { configDefaults } from "vitest/config"; import { configDefaults } from "vitest/config";
import react from "@vitejs/plugin-react";
import { copyFileSync } from "fs";
import { resolve } from "path"; import { resolve } from "path";
const copyCssPlugin = () => ({
name: "copy-css",
closeBundle: () => {
try {
copyFileSync(
"./ts/dist/frontend.css",
"./resources/public/css/ts-style.css",
);
} catch (e) {
console.log("Error copying css file", e);
}
},
});
// https://vitejs.dev/config/ // https://vitejs.dev/config/
import path from "node:path"; import path from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
@@ -14,6 +31,7 @@ const dirname =
// More info at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon // More info at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon
export default defineConfig({ export default defineConfig({
plugins: [react(), copyCssPlugin()],
test: { test: {
exclude: [...configDefaults.exclude, "target/**", "resources/**"], exclude: [...configDefaults.exclude, "target/**", "resources/**"],
environment: "jsdom", environment: "jsdom",
@@ -50,4 +68,22 @@ export default defineConfig({
"@public": resolve(__dirname, "./resources/public/js/"), "@public": resolve(__dirname, "./resources/public/js/"),
}, },
}, },
build: {
outDir: './ts/dist/',
emptyOutDir: true,
lib: {
entry: './ts/src/index.tsx',
fileName: () => `index.js`,
formats: ['es'],
},
rollupOptions: {
external: ['react', 'react-dom'],
output: {
globals: {
react: 'React',
"react-dom": "ReactDOM",
},
},
}
},
}); });

File diff suppressed because it is too large Load Diff