asmer 3 месяцев назад
Сommit
7dae679ad0

+ 30 - 0
README.md

@@ -0,0 +1,30 @@
+# React + TypeScript + Vite
+
+This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
+
+Currently, two official plugins are available:
+
+- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
+
+## Expanding the ESLint configuration
+
+If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
+
+- Configure the top-level `parserOptions` property like this:
+
+```js
+export default {
+  // other rules...
+  parserOptions: {
+    ecmaVersion: 'latest',
+    sourceType: 'module',
+    project: ['./tsconfig.json', './tsconfig.node.json', './tsconfig.app.json'],
+    tsconfigRootDir: __dirname,
+  },
+}
+```
+
+- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
+- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
+- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list

+ 13 - 0
index.html

@@ -0,0 +1,13 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Vite + React + TS</title>
+  </head>
+  <body>
+    <div id="root"></div>
+    <script type="module" src="/src/main.tsx"></script>
+  </body>
+</html>

Разница между файлами не показана из-за своего большого размера
+ 3452 - 0
package-lock.json


+ 28 - 0
package.json

@@ -0,0 +1,28 @@
+{
+  "name": "rayers",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "tsc -b && vite build",
+    "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "react": "^18.3.1",
+    "react-dom": "^18.3.1"
+  },
+  "devDependencies": {
+    "@types/react": "^18.3.3",
+    "@types/react-dom": "^18.3.0",
+    "@typescript-eslint/eslint-plugin": "^7.15.0",
+    "@typescript-eslint/parser": "^7.15.0",
+    "@vitejs/plugin-react": "^4.3.1",
+    "eslint": "^8.57.0",
+    "eslint-plugin-react-hooks": "^4.6.2",
+    "eslint-plugin-react-refresh": "^0.4.7",
+    "typescript": "^5.2.2",
+    "vite": "^5.3.4"
+  }
+}

Разница между файлами не показана из-за своего большого размера
+ 1 - 0
public/vite.svg


+ 42 - 0
src/App.css

@@ -0,0 +1,42 @@
+#root {
+  max-width: 1280px;
+  margin: 0 auto;
+  padding: 2rem;
+  text-align: center;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}

+ 46 - 0
src/App.tsx

@@ -0,0 +1,46 @@
+import { useState, useEffect } from 'react'
+import reactLogo from './assets/react.svg'
+import viteLogo from '/vite.svg'
+import './App.css'
+
+import {createPub, usePub} from './lib';
+import type { Pub } from './lib';
+
+
+const testPub:Pub = createPub({count: 0})
+
+testPub.subscribe(() => console.log(testPub.count))
+
+setInterval(() => testPub.count--, 5000)
+
+
+function App() {
+    const {count} = usePub(testPub)
+
+  return (
+    <>
+      <div>
+        <a href="https://vitejs.dev" target="_blank">
+          <img src={viteLogo} className="logo" alt="Vite logo" />
+        </a>
+        <a href="https://react.dev" target="_blank">
+          <img src={reactLogo} className="logo react" alt="React logo" />
+        </a>
+      </div>
+      <h1>Vite + React</h1>
+      <div className="card">
+        <button onClick={() => testPub.count++}>
+          count is {count}
+        </button>
+        <p>
+          Edit <code>src/App.tsx</code> and save to test HMR
+        </p>
+      </div>
+      <p className="read-the-docs">
+        Click on the Vite and React logos to learn more
+      </p>
+    </>
+  )
+}
+
+export default App

Разница между файлами не показана из-за своего большого размера
+ 1 - 0
src/assets/react.svg


+ 68 - 0
src/index.css

@@ -0,0 +1,68 @@
+:root {
+  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
+  line-height: 1.5;
+  font-weight: 400;
+
+  color-scheme: light dark;
+  color: rgba(255, 255, 255, 0.87);
+  background-color: #242424;
+
+  font-synthesis: none;
+  text-rendering: optimizeLegibility;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+a {
+  font-weight: 500;
+  color: #646cff;
+  text-decoration: inherit;
+}
+a:hover {
+  color: #535bf2;
+}
+
+body {
+  margin: 0;
+  display: flex;
+  place-items: center;
+  min-width: 320px;
+  min-height: 100vh;
+}
+
+h1 {
+  font-size: 3.2em;
+  line-height: 1.1;
+}
+
+button {
+  border-radius: 8px;
+  border: 1px solid transparent;
+  padding: 0.6em 1.2em;
+  font-size: 1em;
+  font-weight: 500;
+  font-family: inherit;
+  background-color: #1a1a1a;
+  cursor: pointer;
+  transition: border-color 0.25s;
+}
+button:hover {
+  border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+  outline: 4px auto -webkit-focus-ring-color;
+}
+
+@media (prefers-color-scheme: light) {
+  :root {
+    color: #213547;
+    background-color: #ffffff;
+  }
+  a:hover {
+    color: #747bff;
+  }
+  button {
+    background-color: #f9f9f9;
+  }
+}

+ 6 - 0
src/lib/index.ts

@@ -0,0 +1,6 @@
+import createPub from './pub';
+
+export {createPub};
+
+export type { Pub } from  './pub'
+export { usePub } from  './pub'

+ 75 - 0
src/lib/pub/createPub.ts

@@ -0,0 +1,75 @@
+interface UnsubscribeFunction {
+    ():void
+}
+
+interface SubscribeFunction {
+    (subscriber:Function):UnsubscribeFunction
+}
+
+export interface Pub {
+    subscribe: SubscribeFunction
+}
+
+
+const createPub = (state:object={}): Pub => {
+    if (typeof state?.subscribe === 'function' || state instanceof Promise) return state
+    
+    let   subscribers:Function[] = []
+    const childUnsubcribers = {}
+    const runSubscribers = (prop:string|symbol , oldValue, newValue) =>  subscribers.forEach(subscriber => subscriber(state, prop, oldValue, newValue))
+    const subscribe      = (subscriber:Function):UnsubscribeFunction    => (subscribers.push(subscriber),
+                                                                              () => {
+                                                                                  subscribers = subscribers.filter(sb => sb !== subscriber)
+                                                                              })
+    
+    state.subscribe = subscribe
+    
+    const proxy = new Proxy(state, {
+        get(obj:object, prop:string|symbol){
+            return obj[prop]
+        },
+        deleteProperty(obj:object, prop:string|symbol){
+            runSubscribers(prop, obj[prop], undefined)
+            if (childUnsubcribers[prop]){
+                childUnsubcribers[prop]()
+                delete childUnsubcribers[prop]
+            }
+            delete obj[prop]
+        },
+        set(obj, prop, value){
+            if (prop === 'subscribe') throw new SyntaxError('You cannot substitute subscribe to other value')
+            const oldValue = obj[prop]
+            if (oldValue === value && ((typeof value !== 'object') || (oldValue && oldValue.subscribe === 'function'))) return true
+            
+            if (oldValue && childUnsubcribers[prop]){
+                childUnsubcribers[prop]()
+                delete childUnsubcribers[prop]
+            }
+
+            if (value && typeof value === 'object' && !childUnsubcribers[prop]){
+                value = createPub(value)
+                if (!(value instanceof Promise)){
+                     // console.log('save child unsubscriber for', prop, childUnsubcribers)
+                    childUnsubcribers[prop] = value.subscribe(value => runSubscribers(prop, null, value))
+                }
+            }
+            
+            obj[prop]      = value
+            runSubscribers(prop, oldValue, value)
+
+            return true;
+        }
+    })
+
+    for (const key in state){
+        
+        if (state[key] && typeof state[key] === 'object'){
+            proxy[key] = state[key]
+        }
+    }
+
+    return proxy
+}
+
+export default createPub;
+

+ 6 - 0
src/lib/pub/index.ts

@@ -0,0 +1,6 @@
+import createPub from './createPub'
+
+export type { Pub } from  './createPub'
+export { usePub } from  './react'
+export default createPub;
+

+ 3 - 0
src/lib/pub/react/hooks/index.ts

@@ -0,0 +1,3 @@
+import usePub from './usePub';
+
+export {usePub};

+ 13 - 0
src/lib/pub/react/hooks/usePub.ts

@@ -0,0 +1,13 @@
+import { useEffect, useState } from 'react';
+import { Pub } from '../../';
+
+export default (pub:Pub):Pub => {
+    const [, setRandomState] = useState<number>()
+    useEffect(() => {
+        const unsubscribe = pub.subscribe(() => setRandomState(Math.random()))
+        return () => {
+            unsubscribe()
+        }
+    }, [pub])
+    return pub
+}

+ 1 - 0
src/lib/pub/react/index.ts

@@ -0,0 +1 @@
+export {usePub} from './hooks';

+ 10 - 0
src/main.tsx

@@ -0,0 +1,10 @@
+import React from 'react'
+import ReactDOM from 'react-dom/client'
+import App from './App.tsx'
+import './index.css'
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+  <React.StrictMode>
+    <App />
+  </React.StrictMode>,
+)

+ 1 - 0
src/vite-env.d.ts

@@ -0,0 +1 @@
+/// <reference types="vite/client" />

+ 27 - 0
tsconfig.app.json

@@ -0,0 +1,27 @@
+{
+  "compilerOptions": {
+    "composite": true,
+    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+    "target": "ES2020",
+    "useDefineForClassFields": true,
+    "lib": ["ES2020", "DOM", "DOM.Iterable"],
+    "module": "ESNext",
+    "skipLibCheck": true,
+
+    /* Bundler mode */
+    "moduleResolution": "bundler",
+    "allowImportingTsExtensions": true,
+    "resolveJsonModule": true,
+    "isolatedModules": true,
+    "moduleDetection": "force",
+    "noEmit": true,
+    "jsx": "react-jsx",
+
+    /* Linting */
+    "strict": true,
+    "noUnusedLocals": true,
+    "noUnusedParameters": true,
+    "noFallthroughCasesInSwitch": true
+  },
+  "include": ["src"]
+}

+ 11 - 0
tsconfig.json

@@ -0,0 +1,11 @@
+{
+  "files": [],
+  "references": [
+    {
+      "path": "./tsconfig.app.json"
+    },
+    {
+      "path": "./tsconfig.node.json"
+    }
+  ]
+}

+ 13 - 0
tsconfig.node.json

@@ -0,0 +1,13 @@
+{
+  "compilerOptions": {
+    "composite": true,
+    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+    "skipLibCheck": true,
+    "module": "ESNext",
+    "moduleResolution": "bundler",
+    "allowSyntheticDefaultImports": true,
+    "strict": true,
+    "noEmit": true
+  },
+  "include": ["vite.config.ts"]
+}

+ 7 - 0
vite.config.ts

@@ -0,0 +1,7 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+  plugins: [react()],
+})