generated from tailored/app-template
	Initial commit
This commit is contained in:
		
							
								
								
									
										11
									
								
								.devcontainer/devcontainer.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								.devcontainer/devcontainer.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "Node.js 22 Development Environment",
 | 
			
		||||
  "image": "mcr.microsoft.com/devcontainers/javascript-node:22",
 | 
			
		||||
  "settings": {
 | 
			
		||||
    "terminal.integrated.defaultProfile.linux": "/bin/bash"
 | 
			
		||||
  },
 | 
			
		||||
  "extensions": [
 | 
			
		||||
    "dbaeumer.vscode-eslint"
 | 
			
		||||
  ],
 | 
			
		||||
  "postCreateCommand": "npm install -g @kevisual/envision-cli@latest && npm install"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
node_modules
 | 
			
		||||
dist
 | 
			
		||||
							
								
								
									
										4
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
# app-template
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
`/system/lib/app.js` 包函的模块是 `QueryRouterServer` 和 `Page` 和 `useConfigKey`
 | 
			
		||||
							
								
								
									
										11
									
								
								config/esbuild.config.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								config/esbuild.config.mjs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
import { build } from 'esbuild';
 | 
			
		||||
 | 
			
		||||
build({
 | 
			
		||||
  entryPoints: ['src/index.ts'],
 | 
			
		||||
  bundle: true,
 | 
			
		||||
  outfile: 'dist/index.js',
 | 
			
		||||
  platform: 'browser',
 | 
			
		||||
  target: 'esnext',
 | 
			
		||||
  sourcemap: false,
 | 
			
		||||
  format: 'esm',
 | 
			
		||||
}).catch(() => process.exit(1));
 | 
			
		||||
							
								
								
									
										31
									
								
								index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								index.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<meta charset="UTF-8">
 | 
			
		||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
			
		||||
<title>AI Apps</title>
 | 
			
		||||
<link rel="stylesheet" href="./src/assets/index.css">
 | 
			
		||||
<style>
 | 
			
		||||
  html,
 | 
			
		||||
  body {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    font-size: 16px;
 | 
			
		||||
  }
 | 
			
		||||
  #ai-root {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
<script src="/system/lib/app.js"></script>
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
  <div id="ai-root"></div>
 | 
			
		||||
  <div id="ai-bot-root"></div>
 | 
			
		||||
</body>
 | 
			
		||||
<script src="./src/main.ts" type="module"></script>
 | 
			
		||||
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										48
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "app-template",
 | 
			
		||||
  "version": "0.0.1",
 | 
			
		||||
  "description": "",
 | 
			
		||||
  "main": "index.js",
 | 
			
		||||
  "basename": "/me/app/",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "dev": "vite",
 | 
			
		||||
    "dev:web": "cross-env WEB_DEV=true vite --mode web",
 | 
			
		||||
    "build": "vite build",
 | 
			
		||||
    "esbuild": "node esbuild.config.mjs",
 | 
			
		||||
    "preview": "vite preview",
 | 
			
		||||
    "prepub": "envision switchOrg user",
 | 
			
		||||
    "pub": "envision deploy ./dist -k app-template -v 0.0.1"
 | 
			
		||||
  },
 | 
			
		||||
  "stackblitz": {
 | 
			
		||||
    "startCommand": "npm dev:web"
 | 
			
		||||
  },
 | 
			
		||||
  "keywords": [],
 | 
			
		||||
  "author": "abearxiong <xiongxiao@xiongxiao.me>",
 | 
			
		||||
  "license": "MIT",
 | 
			
		||||
  "type": "module",
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@floating-ui/dom": "^1.6.13",
 | 
			
		||||
    "@kevisual/query": "0.0.7-alpha.3",
 | 
			
		||||
    "@kevisual/system-lib": "^0.0.10",
 | 
			
		||||
    "@kevisual/system-ui": "^0.0.3",
 | 
			
		||||
    "dayjs": "^1.11.13",
 | 
			
		||||
    "lodash-es": "^4.17.21",
 | 
			
		||||
    "react-dom": "^19.0.0",
 | 
			
		||||
    "zustand": "^5.0.3"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@kevisual/router": "0.0.6-alpha-5",
 | 
			
		||||
    "@kevisual/store": "0.0.1-alpha.10",
 | 
			
		||||
    "@kevisual/types": "^0.0.6",
 | 
			
		||||
    "@tailwindcss/vite": "^4.0.9",
 | 
			
		||||
    "@types/react": "^19.0.8",
 | 
			
		||||
    "@types/react-dom": "^19.0.3",
 | 
			
		||||
    "@vitejs/plugin-basic-ssl": "^1.2.0",
 | 
			
		||||
    "@vitejs/plugin-react": "^4.3.4",
 | 
			
		||||
    "cross-env": "^7.0.3",
 | 
			
		||||
    "esbuild": "^0.25.0",
 | 
			
		||||
    "react": "^19.0.0",
 | 
			
		||||
    "tailwindcss": "^4.0.9",
 | 
			
		||||
    "vite": "^6.1.0"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1686
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1686
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										94
									
								
								snippets/no-react/h.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								snippets/no-react/h.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,94 @@
 | 
			
		||||
// import { nanoid } from 'nanoid';
 | 
			
		||||
import { RefObject, SyntheticEvent } from 'react';
 | 
			
		||||
const randomId = () => {
 | 
			
		||||
  return crypto.getRandomValues(new Uint32Array(1))[0].toString(16);
 | 
			
		||||
};
 | 
			
		||||
const loadChidren = (element: any, children: any[]) => {
 | 
			
		||||
  children.forEach((child) => {
 | 
			
		||||
    if (typeof child === 'boolean') {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (typeof child === 'function') {
 | 
			
		||||
      // console.log('child', child);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (typeof child === 'undefined') {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    // console.log('child', child);
 | 
			
		||||
    element.appendChild(typeof child === 'string' ? document.createTextNode(child) : child);
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
// 在项目中定义 h 函数
 | 
			
		||||
export function h(type: string | Function, props: any, ...children: any[]): HTMLElement {
 | 
			
		||||
  if (typeof type === 'function') {
 | 
			
		||||
    const element = type(props);
 | 
			
		||||
    loadChidren(element, children);
 | 
			
		||||
    return element;
 | 
			
		||||
  }
 | 
			
		||||
  const element = document.createElement(type);
 | 
			
		||||
  const filterKeys = ['onLoad', 'onUnload', 'key'];
 | 
			
		||||
  const key = props?.key || randomId();
 | 
			
		||||
  Object.entries(props || {}).forEach(([key, value]) => {
 | 
			
		||||
    if (filterKeys.includes(key)) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (key === 'className') {
 | 
			
		||||
      element.setAttribute('class', value as string);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (key.startsWith('on')) {
 | 
			
		||||
      element.addEventListener(key.slice(2).toLowerCase(), value as EventListener);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (key === 'ref' && value) {
 | 
			
		||||
      (value as any).current = element;
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (typeof value === 'object') {
 | 
			
		||||
      console.log('error', element, type, value);
 | 
			
		||||
    } else {
 | 
			
		||||
      element.setAttribute(key, value as string);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
  const onLoad = props?.onLoad;
 | 
			
		||||
  const checkConnect = () => {
 | 
			
		||||
    if (element.isConnected) {
 | 
			
		||||
      onLoad?.({ el: element, key, _props: props });
 | 
			
		||||
      // console.log('onLoad', element, key);
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
  };
 | 
			
		||||
  setTimeout(() => {
 | 
			
		||||
    const res = checkConnect();
 | 
			
		||||
    if (!res) {
 | 
			
		||||
      setTimeout(() => {}, 1000);
 | 
			
		||||
    }
 | 
			
		||||
  }, 20);
 | 
			
		||||
 | 
			
		||||
  loadChidren(element, children);
 | 
			
		||||
  return element;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
declare global {
 | 
			
		||||
  namespace JSX {
 | 
			
		||||
    // type Element = HTMLElement; // 将 JSX.Element 设置为 HTMLElement
 | 
			
		||||
    interface Element extends HTMLElement {
 | 
			
		||||
      class?: string;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  namespace React {
 | 
			
		||||
    interface FormEvent<T = Element> extends SyntheticEvent<T> {
 | 
			
		||||
      target: EventTarget & (T extends HTMLInputElement ? HTMLInputElement : T);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const useRef = <T = HTMLDivElement>(initialValue: T | null = null): RefObject<T | null> => {
 | 
			
		||||
  return { current: initialValue };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const useEffect = (callback: () => void) => {
 | 
			
		||||
  setTimeout(callback, 0);
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										6
									
								
								snippets/react/ReactApp.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								snippets/react/ReactApp.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
import { createRoot } from 'react-dom/client';
 | 
			
		||||
 | 
			
		||||
export const ReactApp = () => {
 | 
			
		||||
  const root = createRoot(document.getElementById('app')!);
 | 
			
		||||
  root.render(<div>Hello, World!</div>);
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										12
									
								
								snippets/routes-app/app.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								snippets/routes-app/app.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
import type { Page } from '@kevisual/store/page';
 | 
			
		||||
import type { QueryRouterServer } from '@kevisual/router';
 | 
			
		||||
export const page = useContextKey('page', () => {
 | 
			
		||||
  return new window.Page({
 | 
			
		||||
    basename: '',
 | 
			
		||||
  }) as unknown as Page;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const app = useContextKey('app', () => {
 | 
			
		||||
  console.error('app not found');
 | 
			
		||||
  return null as unknown as QueryRouterServer;
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										39
									
								
								snippets/routes-app/main.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								snippets/routes-app/main.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
import { page, app } from './app.ts';
 | 
			
		||||
export const render = ({ renderRoot }) => {
 | 
			
		||||
  renderRoot.innerHTML = `
 | 
			
		||||
    <h1>Hello, World!</h1>
 | 
			
		||||
  `;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
if (page) {
 | 
			
		||||
  page.addPage('/app-template', 'home');
 | 
			
		||||
  page.subscribe('home', () => {
 | 
			
		||||
    render({
 | 
			
		||||
      renderRoot: document.getElementById('ai-root'),
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if (app) {
 | 
			
		||||
  app
 | 
			
		||||
    .route({
 | 
			
		||||
      path: 'app-template',
 | 
			
		||||
      key: 'render',
 | 
			
		||||
    })
 | 
			
		||||
    .define(async (ctx) => {
 | 
			
		||||
      let { renderRoot } = ctx.query;
 | 
			
		||||
      if (!renderRoot) {
 | 
			
		||||
        ctx.throw(404, 'renderRoot is required');
 | 
			
		||||
      }
 | 
			
		||||
      if (typeof renderRoot === 'string') {
 | 
			
		||||
        renderRoot = document.querySelector(renderRoot);
 | 
			
		||||
      }
 | 
			
		||||
      if (!renderRoot) {
 | 
			
		||||
        ctx.throw(404, 'renderRoot not found');
 | 
			
		||||
      }
 | 
			
		||||
      render({
 | 
			
		||||
        renderRoot,
 | 
			
		||||
      });
 | 
			
		||||
    })
 | 
			
		||||
    .addTo(app);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										85
									
								
								snippets/store/app.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								snippets/store/app.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,85 @@
 | 
			
		||||
import { create } from 'zustand';
 | 
			
		||||
import { query } from '@/modules/query';
 | 
			
		||||
type Store = {
 | 
			
		||||
  list: any[];
 | 
			
		||||
  setList: (list: any[]) => void;
 | 
			
		||||
  data: any;
 | 
			
		||||
  setData: (data: any) => void;
 | 
			
		||||
  loading: boolean;
 | 
			
		||||
  setLoading: (loading: boolean) => void;
 | 
			
		||||
  formData: any;
 | 
			
		||||
  setFormData: (data: any) => void;
 | 
			
		||||
  getList: () => Promise<any>;
 | 
			
		||||
  init: () => Promise<void>;
 | 
			
		||||
  getData: (id: number) => Promise<any>;
 | 
			
		||||
  updateData: (data: any, opts?: { refresh?: boolean }) => Promise<any>;
 | 
			
		||||
  deleteData: (id: number, opts?: { refresh?: boolean }) => Promise<any>;
 | 
			
		||||
};
 | 
			
		||||
export const useStore = create<Store>((set, get) => ({
 | 
			
		||||
  list: [],
 | 
			
		||||
  setList: (list) => set({ list }),
 | 
			
		||||
  data: null,
 | 
			
		||||
  setData: (data) => set({ data }),
 | 
			
		||||
  loading: false,
 | 
			
		||||
  setLoading: (loading) => set({ loading }),
 | 
			
		||||
  formData: null,
 | 
			
		||||
  setFormData: (formData) => set({ formData }),
 | 
			
		||||
  getList: async () => {
 | 
			
		||||
    set({ loading: true });
 | 
			
		||||
    const res = await query.post({ path: 'posts', key: 'list' });
 | 
			
		||||
    set({ loading: false });
 | 
			
		||||
    if (res.code === 200) {
 | 
			
		||||
      set({ list: res.data });
 | 
			
		||||
    }
 | 
			
		||||
    return res;
 | 
			
		||||
  },
 | 
			
		||||
  init: async () => {
 | 
			
		||||
    await get().getList();
 | 
			
		||||
  },
 | 
			
		||||
  getData: async (id) => {
 | 
			
		||||
    set({ loading: true });
 | 
			
		||||
    const res = await query.post({
 | 
			
		||||
      path: 'posts',
 | 
			
		||||
      key: 'get',
 | 
			
		||||
      id,
 | 
			
		||||
    });
 | 
			
		||||
    set({ loading: false });
 | 
			
		||||
    if (res.code === 200) {
 | 
			
		||||
      const data = res.data;
 | 
			
		||||
      set({ data });
 | 
			
		||||
    }
 | 
			
		||||
    return res;
 | 
			
		||||
  },
 | 
			
		||||
  updateData: async (data, opts = { refresh: true }) => {
 | 
			
		||||
    set({ loading: true });
 | 
			
		||||
    const res = await query.post({
 | 
			
		||||
      path: 'posts',
 | 
			
		||||
      key: 'update',
 | 
			
		||||
      data,
 | 
			
		||||
    });
 | 
			
		||||
    set({ loading: false });
 | 
			
		||||
    if (res.code === 200) {
 | 
			
		||||
      set({ data: res.data });
 | 
			
		||||
    }
 | 
			
		||||
    if (opts.refresh) {
 | 
			
		||||
      await get().getList();
 | 
			
		||||
    }
 | 
			
		||||
    return res;
 | 
			
		||||
  },
 | 
			
		||||
  deleteData: async (id, opts = { refresh: true }) => {
 | 
			
		||||
    set({ loading: true });
 | 
			
		||||
    const res = await query.post({
 | 
			
		||||
      path: 'posts',
 | 
			
		||||
      key: 'delete',
 | 
			
		||||
      id,
 | 
			
		||||
    });
 | 
			
		||||
    set({ loading: false });
 | 
			
		||||
    if (res.code === 200) {
 | 
			
		||||
      set({ data: null });
 | 
			
		||||
    }
 | 
			
		||||
    if (opts.refresh) {
 | 
			
		||||
      await get().getList();
 | 
			
		||||
    }
 | 
			
		||||
    return res;
 | 
			
		||||
  },
 | 
			
		||||
}));
 | 
			
		||||
							
								
								
									
										12
									
								
								src/app.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/app.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
import type { Page } from '@kevisual/store/page';
 | 
			
		||||
import type { QueryRouterServer } from '@kevisual/router';
 | 
			
		||||
import { basename } from './modules/basename';
 | 
			
		||||
export const page = useContextKey('page', () => {
 | 
			
		||||
  return new window.Page({
 | 
			
		||||
    basename,
 | 
			
		||||
  }) as unknown as Page;
 | 
			
		||||
});
 | 
			
		||||
export const app = useContextKey('app', () => {
 | 
			
		||||
  console.error('app not found');
 | 
			
		||||
  return null as unknown as QueryRouterServer;
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										16
									
								
								src/assets/index.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/assets/index.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
@import "tailwindcss";
 | 
			
		||||
 | 
			
		||||
@layer components {
 | 
			
		||||
  .test-loading {
 | 
			
		||||
    @apply w-20 h-20 bg-gray-300 rounded-full animate-spin;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ai-bot-root {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  left: -100px;
 | 
			
		||||
  z-index: 9999;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										48
									
								
								src/main.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/main.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
import { page, app } from './app.ts';
 | 
			
		||||
import { basename } from './modules/basename.ts';
 | 
			
		||||
export const render = ({ renderRoot }) => {
 | 
			
		||||
  renderRoot.innerHTML = `
 | 
			
		||||
    <h1>Hello, World!</h1>
 | 
			
		||||
  `;
 | 
			
		||||
};
 | 
			
		||||
console.log('basename', basename, page, app);
 | 
			
		||||
 | 
			
		||||
if (page) {
 | 
			
		||||
  page.addPage('/app-template', 'home');
 | 
			
		||||
  page.subscribe('home', () => {
 | 
			
		||||
    render({
 | 
			
		||||
      renderRoot: document.getElementById('ai-root'),
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
  page.addPage('', 'index');
 | 
			
		||||
  page.subscribe('index', () => {
 | 
			
		||||
    const root = document.getElementById('ai-root') as HTMLElement;
 | 
			
		||||
    root.innerHTML = `
 | 
			
		||||
      <h1>Hello, World!</h1>
 | 
			
		||||
    `;
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if (app) {
 | 
			
		||||
  app
 | 
			
		||||
    .route({
 | 
			
		||||
      path: 'app-template',
 | 
			
		||||
      key: 'render',
 | 
			
		||||
    })
 | 
			
		||||
    .define(async (ctx) => {
 | 
			
		||||
      let { renderRoot } = ctx.query;
 | 
			
		||||
      if (!renderRoot) {
 | 
			
		||||
        ctx.throw(404, 'renderRoot is required');
 | 
			
		||||
      }
 | 
			
		||||
      if (typeof renderRoot === 'string') {
 | 
			
		||||
        renderRoot = document.querySelector(renderRoot);
 | 
			
		||||
      }
 | 
			
		||||
      if (!renderRoot) {
 | 
			
		||||
        ctx.throw(404, 'renderRoot not found');
 | 
			
		||||
      }
 | 
			
		||||
      render({
 | 
			
		||||
        renderRoot,
 | 
			
		||||
      });
 | 
			
		||||
    })
 | 
			
		||||
    .addTo(app);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										2
									
								
								src/modules/basename.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/modules/basename.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
export const basename = DEV_SERVER ? '/' : BASE_NAME;
 | 
			
		||||
							
								
								
									
										3
									
								
								src/modules/message.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/modules/message.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
import { message } from '@kevisual/system-ui/dist/message';
 | 
			
		||||
 | 
			
		||||
export { message };
 | 
			
		||||
							
								
								
									
										3
									
								
								src/modules/query.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/modules/query.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
import { QueryClient } from '@kevisual/query';
 | 
			
		||||
 | 
			
		||||
export const query = new QueryClient();
 | 
			
		||||
							
								
								
									
										43
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
{
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "jsx": "react",
 | 
			
		||||
    "target": "ES2020",
 | 
			
		||||
    "useDefineForClassFields": true,
 | 
			
		||||
    "lib": [
 | 
			
		||||
      "ES2020",
 | 
			
		||||
      "DOM",
 | 
			
		||||
      "DOM.Iterable"
 | 
			
		||||
    ],
 | 
			
		||||
    "module": "ESNext",
 | 
			
		||||
    "skipLibCheck": true,
 | 
			
		||||
    /* Bundler mode */
 | 
			
		||||
    "moduleResolution": "bundler",
 | 
			
		||||
    "allowImportingTsExtensions": true,
 | 
			
		||||
    "isolatedModules": true,
 | 
			
		||||
    "moduleDetection": "force",
 | 
			
		||||
    "noEmit": true,
 | 
			
		||||
    // "jsxFragmentFactory": "Fragment",
 | 
			
		||||
    // "jsxFactory": "h",
 | 
			
		||||
    "baseUrl": "./",
 | 
			
		||||
    "typeRoots": [
 | 
			
		||||
      "node_modules/@types",
 | 
			
		||||
      "node_modules/@kevisual/types",
 | 
			
		||||
    ],
 | 
			
		||||
    "paths": {
 | 
			
		||||
      "@/*": [
 | 
			
		||||
        "src/*"
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    /* Linting */
 | 
			
		||||
    "strict": true,
 | 
			
		||||
    "noImplicitAny": false,
 | 
			
		||||
    "noUnusedLocals": false,
 | 
			
		||||
    "noUnusedParameters": false,
 | 
			
		||||
    "noFallthroughCasesInSwitch": true
 | 
			
		||||
  },
 | 
			
		||||
  "include": [
 | 
			
		||||
    "src",
 | 
			
		||||
    "typings.d.ts",
 | 
			
		||||
    "snippets"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										55
									
								
								vite.config.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								vite.config.mjs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
import { defineConfig } from 'vite';
 | 
			
		||||
import basicSsl from '@vitejs/plugin-basic-ssl';
 | 
			
		||||
// import react from '@vitejs/plugin-react';
 | 
			
		||||
import dayjs from 'dayjs';
 | 
			
		||||
import path from 'path';
 | 
			
		||||
import tailwindcss from '@tailwindcss/vite'
 | 
			
		||||
import pkgs from './package.json' with { type: 'json' };
 | 
			
		||||
 | 
			
		||||
const isDev = process.env.NODE_ENV === 'development';
 | 
			
		||||
const isWebDev = process.env.WEB_DEV === 'true';
 | 
			
		||||
const BUILD_TIME = dayjs().format('YYYY-MM-DD HH:mm:ss');
 | 
			
		||||
const basename = pkgs.basename;
 | 
			
		||||
let plugins = [tailwindcss(),];
 | 
			
		||||
 | 
			
		||||
if (!isWebDev) {
 | 
			
		||||
  // 在bolt的web开发环境下不需要ssl
 | 
			
		||||
  plugins.push(basicSsl());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export default defineConfig({
 | 
			
		||||
  plugins: plugins,
 | 
			
		||||
  resolve: {
 | 
			
		||||
    alias: {
 | 
			
		||||
      '@': path.resolve(__dirname, './src'),
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  base: isDev ? '/' : basename,
 | 
			
		||||
  define: {
 | 
			
		||||
    DEV_SERVER: JSON.stringify(isDev),
 | 
			
		||||
    BUILD_TIME: JSON.stringify(BUILD_TIME),
 | 
			
		||||
    BASE_NAME: JSON.stringify(basename),
 | 
			
		||||
  },
 | 
			
		||||
  optimizeDeps: {
 | 
			
		||||
    exclude: ['react'], // 排除 react 和 react-dom 以避免打包
 | 
			
		||||
  },
 | 
			
		||||
  // esbuild: {
 | 
			
		||||
  //   jsxFactory: 'h',
 | 
			
		||||
  //   jsxFragment: 'Fragment',
 | 
			
		||||
  // },
 | 
			
		||||
  server: {
 | 
			
		||||
    port: 6025,
 | 
			
		||||
    host: '0.0.0.0',
 | 
			
		||||
    proxy: {
 | 
			
		||||
      '/api': {
 | 
			
		||||
        target: 'https://kevisual.xiongxiao.me',
 | 
			
		||||
        changeOrigin: true,
 | 
			
		||||
      },
 | 
			
		||||
      '/system/lib': {
 | 
			
		||||
        target: 'https://kevisual.xiongxiao.me',
 | 
			
		||||
        changeOrigin: true,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
		Reference in New Issue
	
	Block a user