diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..32ad81f35 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "proseWrap": "never", + "trailingComma": "all" +} diff --git a/src/js/README.md b/src/js/README.md deleted file mode 100644 index e99df49c0..000000000 --- a/src/js/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# ReactPy Client - -An ES6 Javascript client for ReactPy diff --git a/src/js/bun.lockb b/src/js/bun.lockb index ff28eeb7f..d668a4403 100644 Binary files a/src/js/bun.lockb and b/src/js/bun.lockb differ diff --git a/src/js/eslint.config.mjs b/src/js/eslint.config.mjs index 7509afebe..12494f73d 100644 --- a/src/js/eslint.config.mjs +++ b/src/js/eslint.config.mjs @@ -1,58 +1,25 @@ -import react from "eslint-plugin-react"; -import typescriptEslint from "@typescript-eslint/eslint-plugin"; +import { default as eslint } from "@eslint/js"; import globals from "globals"; -import tsParser from "@typescript-eslint/parser"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import js from "@eslint/js"; -import { FlatCompat } from "@eslint/eslintrc"; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended, - allConfig: js.configs.all, -}); +import tseslint from "typescript-eslint"; export default [ - ...compat.extends( - "eslint:recommended", - "plugin:react/recommended", - "plugin:@typescript-eslint/recommended", - ), - { - ignores: ["**/node_modules/", "**/dist/"], - }, + eslint.configs.recommended, + ...tseslint.configs.recommended, + { ignores: ["**/node_modules/", "**/dist/"] }, { - plugins: { - react, - "@typescript-eslint": typescriptEslint, - }, - languageOptions: { globals: { ...globals.browser, ...globals.node, }, - - parser: tsParser, ecmaVersion: "latest", sourceType: "module", }, - - settings: { - react: { - version: "detect", - }, - }, - rules: { "@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-non-null-assertion": "off", "@typescript-eslint/no-empty-function": "off", - "react/prop-types": "off", }, }, ]; diff --git a/src/js/package.json b/src/js/package.json index 26962b2c6..03f49afa0 100644 --- a/src/js/package.json +++ b/src/js/package.json @@ -1,17 +1,15 @@ { "devDependencies": { - "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "^9.18.0", - "@typescript-eslint/eslint-plugin": "^8.21.0", - "@typescript-eslint/parser": "^8.21.0", + "@eslint/js": "^9.29.0", + "bun-types": "^1.2.16", "eslint": "^9.18.0", - "eslint-plugin-react": "^7.37.4", "globals": "^15.14.0", - "prettier": "^3.4.2" + "prettier": "^3.4.2", + "typescript-eslint": "^8.34.0" }, "license": "MIT", "scripts": { - "lint": "prettier --check . && eslint", - "format": "prettier --write . && eslint --fix" + "format": "prettier --write . && eslint --fix", + "lint": "prettier --check . && eslint" } } diff --git a/src/js/packages/@reactpy/app/bun.lockb b/src/js/packages/@reactpy/app/bun.lockb index bd09c30d6..5c921795b 100644 Binary files a/src/js/packages/@reactpy/app/bun.lockb and b/src/js/packages/@reactpy/app/bun.lockb differ diff --git a/src/js/packages/@reactpy/app/eslint.config.mjs b/src/js/packages/@reactpy/app/eslint.config.mjs deleted file mode 100644 index 7c41582b5..000000000 --- a/src/js/packages/@reactpy/app/eslint.config.mjs +++ /dev/null @@ -1,42 +0,0 @@ -import react from "eslint-plugin-react"; -import typescriptEslint from "@typescript-eslint/eslint-plugin"; -import globals from "globals"; -import tsParser from "@typescript-eslint/parser"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import js from "@eslint/js"; -import { FlatCompat } from "@eslint/eslintrc"; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended, - allConfig: js.configs.all, -}); - -export default [ - ...compat.extends( - "eslint:recommended", - "plugin:react/recommended", - "plugin:@typescript-eslint/recommended", - ), - { - plugins: { - react, - "@typescript-eslint": typescriptEslint, - }, - - languageOptions: { - globals: { - ...globals.browser, - }, - - parser: tsParser, - ecmaVersion: "latest", - sourceType: "module", - }, - - rules: {}, - }, -]; diff --git a/src/js/packages/@reactpy/app/package.json b/src/js/packages/@reactpy/app/package.json index ca3a4370d..3239e974e 100644 --- a/src/js/packages/@reactpy/app/package.json +++ b/src/js/packages/@reactpy/app/package.json @@ -1,17 +1,17 @@ { - "name": "@reactpy/app", - "description": "ReactPy's client-side entry point. This is strictly for internal use and is not designed to be distributed.", - "license": "MIT", "dependencies": { "@reactpy/client": "file:../client", "event-to-object": "file:../../event-to-object", "preact": "^10.25.4" }, + "description": "ReactPy's client-side entry point. This is strictly for internal use and is not designed to be distributed.", "devDependencies": { - "typescript": "^5.7.3", "@pyscript/core": "^0.6", - "morphdom": "^2" + "morphdom": "^2", + "typescript": "^5.8.3" }, + "license": "MIT", + "name": "@reactpy/app", "scripts": { "build": "bun build \"src/index.ts\" --outdir=\"../../../../reactpy/static/\" --minify --sourcemap=\"linked\"", "checkTypes": "tsc --noEmit" diff --git a/src/js/packages/@reactpy/app/tsconfig.json b/src/js/packages/@reactpy/app/tsconfig.json index fb7013663..e7c43004b 100644 --- a/src/js/packages/@reactpy/app/tsconfig.json +++ b/src/js/packages/@reactpy/app/tsconfig.json @@ -1,10 +1,10 @@ { - "extends": "../../../tsconfig.json", "compilerOptions": { + "composite": true, "outDir": "dist", - "rootDir": "src", - "composite": true + "rootDir": "src" }, + "extends": "../../../tsconfig.json", "include": ["src"], "references": [ { diff --git a/src/js/packages/@reactpy/client/bun.lockb b/src/js/packages/@reactpy/client/bun.lockb index 35e6ef1c5..10334c0e5 100644 Binary files a/src/js/packages/@reactpy/client/bun.lockb and b/src/js/packages/@reactpy/client/bun.lockb differ diff --git a/src/js/packages/@reactpy/client/package.json b/src/js/packages/@reactpy/client/package.json index 95e545eb4..1016f94f1 100644 --- a/src/js/packages/@reactpy/client/package.json +++ b/src/js/packages/@reactpy/client/package.json @@ -1,12 +1,13 @@ { - "name": "@reactpy/client", - "version": "1.0.0", - "description": "A client for ReactPy implemented in React", "author": "Ryan Morshead", - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/reactive-python/reactpy" + "dependencies": { + "json-pointer": "^0.6.2", + "preact": "^10.26.9" + }, + "description": "A client for ReactPy implemented in React", + "devDependencies": { + "@types/json-pointer": "^1.0.34", + "typescript": "^5.8.3" }, "keywords": [ "react", @@ -14,23 +15,20 @@ "python", "reactpy" ], - "type": "module", + "license": "MIT", "main": "dist/index.js", - "dependencies": { - "json-pointer": "^0.6.2", - "preact": "^10.25.4" - }, - "devDependencies": { - "@types/json-pointer": "^1.0.34", - "@types/react": "^17.0", - "@types/react-dom": "^17.0", - "typescript": "^5.7.3" - }, + "name": "@reactpy/client", "peerDependencies": { "event-to-object": "<1.0.0" }, + "repository": { + "type": "git", + "url": "https://github.com/reactive-python/reactpy" + }, "scripts": { "build": "tsc -b", "checkTypes": "tsc --noEmit" - } + }, + "type": "module", + "version": "1.0.0" } diff --git a/src/js/packages/@reactpy/client/src/client.ts b/src/js/packages/@reactpy/client/src/client.ts index ea4e1aed5..8d9333094 100644 --- a/src/js/packages/@reactpy/client/src/client.ts +++ b/src/js/packages/@reactpy/client/src/client.ts @@ -1,5 +1,5 @@ import logger from "./logger"; -import { +import type { ReactPyClientInterface, ReactPyModule, GenericReactPyClientProps, diff --git a/src/js/packages/@reactpy/client/src/components.tsx b/src/js/packages/@reactpy/client/src/components.tsx index 42f303198..a4fa97ce3 100644 --- a/src/js/packages/@reactpy/client/src/components.tsx +++ b/src/js/packages/@reactpy/client/src/components.tsx @@ -1,16 +1,8 @@ import { set as setJsonPointer } from "json-pointer"; -import React, { - ChangeEvent, - createContext, - createElement, - Fragment, - MutableRefObject, - useContext, - useEffect, - useRef, - useState, -} from "preact/compat"; -import { +import type { ChangeEvent, MutableRefObject } from "preact/compat"; +import { createContext, createElement, Fragment, type JSX } from "preact"; +import { useContext, useEffect, useRef, useState } from "preact/hooks"; +import type { ImportSourceBinding, ReactPyComponent, ReactPyVdom, @@ -67,7 +59,7 @@ export function Element({ model }: { model: ReactPyVdom }): JSX.Element | null { } function StandardElement({ model }: { model: ReactPyVdom }) { - const client = React.useContext(ClientContext); + const client = useContext(ClientContext); // Use createElement here to avoid warning about variable numbers of children not // having keys. Warning about this must now be the responsibility of the client // providing the models instead of the client rendering them. @@ -83,10 +75,10 @@ function StandardElement({ model }: { model: ReactPyVdom }) { function UserInputElement({ model }: { model: ReactPyVdom }): JSX.Element { const client = useContext(ClientContext); const props = createAttributes(model, client); - const [value, setValue] = React.useState(props.value); + const [value, setValue] = useState(props.value); // honor changes to value from the client via props - React.useEffect(() => setValue(props.value), [props.value]); + useEffect(() => setValue(props.value), [props.value]); const givenOnChange = props.onChange; if (typeof givenOnChange === "function") { @@ -116,7 +108,7 @@ function UserInputElement({ model }: { model: ReactPyVdom }): JSX.Element { function ScriptElement({ model }: { model: ReactPyVdom }) { const ref = useRef(null); - React.useEffect(() => { + useEffect(() => { // Don't run if the parent element is missing if (!ref.current) { return; @@ -181,10 +173,10 @@ function useImportSource(model: ReactPyVdom): MutableRefObject { const vdomImportSource = model.importSource; const vdomImportSourceJsonString = JSON.stringify(vdomImportSource); const mountPoint = useRef(null); - const client = React.useContext(ClientContext); + const client = useContext(ClientContext); const [binding, setBinding] = useState(null); - React.useEffect(() => { + useEffect(() => { let unmounted = false; if (vdomImportSource) { diff --git a/src/js/packages/@reactpy/client/src/mount.tsx b/src/js/packages/@reactpy/client/src/mount.tsx index 55ba4245e..281f8291e 100644 --- a/src/js/packages/@reactpy/client/src/mount.tsx +++ b/src/js/packages/@reactpy/client/src/mount.tsx @@ -1,7 +1,7 @@ -import { default as React, default as ReactDOM } from "preact/compat"; +import { render } from "preact"; import { ReactPyClient } from "./client"; import { Layout } from "./components"; -import { MountProps } from "./types"; +import type { MountProps } from "./types"; export function mountReactPy(props: MountProps) { // WebSocket route for component rendering @@ -36,6 +36,5 @@ export function mountReactPy(props: MountProps) { }); // Start rendering the component - // eslint-disable-next-line react/no-deprecated - ReactDOM.render(, props.mountElement); + render(, props.mountElement); } diff --git a/src/js/packages/@reactpy/client/src/types.ts b/src/js/packages/@reactpy/client/src/types.ts index 148a3486c..49232e532 100644 --- a/src/js/packages/@reactpy/client/src/types.ts +++ b/src/js/packages/@reactpy/client/src/types.ts @@ -1,4 +1,4 @@ -import { ComponentType } from "react"; +import type { ComponentType } from "preact"; // #### CONNECTION TYPES #### diff --git a/src/js/packages/@reactpy/client/src/vdom.tsx b/src/js/packages/@reactpy/client/src/vdom.tsx index 4bd882ff4..f54111de7 100644 --- a/src/js/packages/@reactpy/client/src/vdom.tsx +++ b/src/js/packages/@reactpy/client/src/vdom.tsx @@ -1,7 +1,6 @@ -import React from "react"; -import { ReactPyClientInterface } from "./types"; +import type { ReactPyClientInterface } from "./types"; import serializeEvent from "event-to-object"; -import { +import type { ReactPyVdom, ReactPyVdomImportSource, ReactPyVdomEventHandler, @@ -112,7 +111,7 @@ function getComponentFromModule( /* Gets the component with the provided name from the provided module. Built specifically to work on inifinitely deep nested components. - For example, component "My.Nested.Component" is accessed from + For example, component "My.Nested.Component" is accessed from ModuleA like so: ModuleA["My"]["Nested"]["Component"]. */ const componentParts: string[] = componentName.split("."); @@ -206,17 +205,14 @@ function createEventHandler( ): [string, () => void] { const eventHandler = function (...args: any[]) { const data = Array.from(args).map((value) => { - if (!(typeof value === "object" && value.nativeEvent)) { - return value; - } - const event = value as React.SyntheticEvent; + const event = value as Event; if (preventDefault) { event.preventDefault(); } if (stopPropagation) { event.stopPropagation(); } - return serializeEvent(event.nativeEvent); + return serializeEvent(event); }); client.sendMessage({ type: "layout-event", data, target }); }; @@ -228,7 +224,7 @@ function createInlineJavaScript( name: string, inlineJavaScript: string, ): [string, () => void] { - /* Function that will execute the string-like InlineJavaScript + /* Function that will execute the string-like InlineJavaScript via eval in the most appropriate way */ const wrappedExecutable = function (...args: any[]) { function handleExecution(...args: any[]) { diff --git a/src/js/packages/@reactpy/client/src/websocket.ts b/src/js/packages/@reactpy/client/src/websocket.ts index ba3fdc09f..4c72620f0 100644 --- a/src/js/packages/@reactpy/client/src/websocket.ts +++ b/src/js/packages/@reactpy/client/src/websocket.ts @@ -1,4 +1,4 @@ -import { CreateReconnectingWebSocketProps } from "./types"; +import type { CreateReconnectingWebSocketProps } from "./types"; import log from "./logger"; export function createReconnectingWebSocket( diff --git a/src/js/packages/@reactpy/client/tsconfig.json b/src/js/packages/@reactpy/client/tsconfig.json index 032152ae8..b6bb436d0 100644 --- a/src/js/packages/@reactpy/client/tsconfig.json +++ b/src/js/packages/@reactpy/client/tsconfig.json @@ -1,10 +1,10 @@ { - "extends": "../../../tsconfig.json", "compilerOptions": { + "composite": true, "outDir": "dist", - "rootDir": "src", - "composite": true + "rootDir": "src" }, + "extends": "../../../tsconfig.json", "include": ["src"], "references": [ { diff --git a/src/js/packages/event-to-object/bun.lockb b/src/js/packages/event-to-object/bun.lockb index d3ca6e7e5..1c6a0e669 100644 Binary files a/src/js/packages/event-to-object/bun.lockb and b/src/js/packages/event-to-object/bun.lockb differ diff --git a/src/js/packages/event-to-object/package.json b/src/js/packages/event-to-object/package.json index 2f3852120..6d2af6dde 100644 --- a/src/js/packages/event-to-object/package.json +++ b/src/js/packages/event-to-object/package.json @@ -1,34 +1,34 @@ { - "name": "event-to-object", - "version": "0.1.2", - "description": "Converts a JavaScript events to JSON serializable objects.", "author": "Ryan Morshead", - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/reactive-python/reactpy" - }, - "keywords": [ - "event", - "json", - "object", - "convert" - ], - "type": "module", - "main": "dist/index.js", "dependencies": { "json-pointer": "^0.6.2" }, + "description": "Converts a JavaScript events to JSON serializable objects.", "devDependencies": { "happy-dom": "^8.9.0", "lodash": "^4.17.21", "tsm": "^2.3.0", - "typescript": "^5.7.3", + "typescript": "^5.8.3", "uvu": "^0.5.6" }, + "keywords": [ + "event", + "json", + "object", + "convert" + ], + "license": "MIT", + "main": "dist/index.js", + "name": "event-to-object", + "repository": { + "type": "git", + "url": "https://github.com/reactive-python/reactpy" + }, "scripts": { "build": "tsc -b", "checkTypes": "tsc --noEmit", "test": "uvu -r tsm tests" - } + }, + "type": "module", + "version": "0.1.2" } diff --git a/src/js/packages/event-to-object/src/index.ts b/src/js/packages/event-to-object/src/index.ts index 8790add04..3f263af4d 100644 --- a/src/js/packages/event-to-object/src/index.ts +++ b/src/js/packages/event-to-object/src/index.ts @@ -11,6 +11,7 @@ export default function convert( ? P : never; }[keyof e.EventToObjectMap] + | e.EventObject | null { return event.type in eventConverters ? eventConverters[event.type](event) diff --git a/src/js/packages/event-to-object/tsconfig.json b/src/js/packages/event-to-object/tsconfig.json index 9b0e0b6a5..4fec695a9 100644 --- a/src/js/packages/event-to-object/tsconfig.json +++ b/src/js/packages/event-to-object/tsconfig.json @@ -1,9 +1,9 @@ { - "extends": "../../tsconfig.json", "compilerOptions": { + "composite": true, "outDir": "dist", - "rootDir": "src", - "composite": true + "rootDir": "src" }, + "extends": "../../tsconfig.json", "include": ["src"] } diff --git a/src/js/tsconfig.json b/src/js/tsconfig.json index 9e7fe5f74..aa44c2023 100644 --- a/src/js/tsconfig.json +++ b/src/js/tsconfig.json @@ -1,22 +1,31 @@ { "compilerOptions": { - "allowJs": false, + "allowJs": true, "allowSyntheticDefaultImports": true, "declaration": true, "declarationMap": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "isolatedModules": true, - "jsx": "react", - "lib": ["DOM", "DOM.Iterable", "esnext"], - "module": "esnext", - "moduleResolution": "node", + "jsx": "react-jsx", + "jsxImportSource": "preact", + "lib": ["ESNext", "DOM", "DOM.Iterable"], + "module": "Preserve", + "moduleDetection": "force", + "moduleResolution": "bundler", "noEmitOnError": true, "noUnusedLocals": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"], + "react-dom/*": ["./node_modules/preact/compat/*"], + "react/jsx-runtime": ["./node_modules/preact/jsx-runtime"] + }, "resolveJsonModule": true, - "skipLibCheck": false, + "skipLibCheck": true, "sourceMap": true, "strict": true, - "target": "esnext" + "target": "ESNext", + "verbatimModuleSyntax": true } } diff --git a/src/reactpy/executors/utils.py b/src/reactpy/executors/utils.py index e0008df9a..b68d56627 100644 --- a/src/reactpy/executors/utils.py +++ b/src/reactpy/executors/utils.py @@ -43,9 +43,7 @@ def vdom_head_to_html(head: VdomDict) -> str: if isinstance(head, dict) and head.get("tagName") == "head": return reactpy_to_string(head) - raise ValueError( - "Invalid head element! Element must be either `html.head` or a string." - ) + raise ValueError("Head element must be constructed with `html.head`.") def process_settings(settings: ReactPyConfig) -> None: diff --git a/tests/test_asgi/test_utils.py b/tests/test_asgi/test_utils.py index f0ffc5a73..7b65b2d80 100644 --- a/tests/test_asgi/test_utils.py +++ b/tests/test_asgi/test_utils.py @@ -5,7 +5,7 @@ def test_invalid_vdom_head(): - with pytest.raises(ValueError, match="Invalid head element!*"): + with pytest.raises(ValueError): utils.vdom_head_to_html({"tagName": "invalid"})