feat: add info

This commit is contained in:
zhuzhengjian
2025-09-02 10:50:48 +08:00
commit 8053fdfc37
33 changed files with 5176 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

3
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# Penna App
这是一个Penna的基础框架支持多应用开发模式

13
apps/play/index.html Normal file
View File

@ -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 + Vue + TS</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

23
apps/play/package.json Normal file
View File

@ -0,0 +1,23 @@
{
"name": "play",
"type": "module",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@unocss/reset": "^66.5.0",
"unocss": "^66.5.0",
"vue": "^3.5.18"
},
"devDependencies": {
"@vitejs/plugin-vue": "^6.0.1",
"@vue/tsconfig": "^0.7.0",
"typescript": "~5.8.3",
"vite": "^7.1.2",
"vue-tsc": "^3.0.5"
}
}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

30
apps/play/src/App.vue Normal file
View File

@ -0,0 +1,30 @@
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<div>
<a href="https://vite.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
</div>
<HelloWorld msg="Vite + Vue" />
</template>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

After

Width:  |  Height:  |  Size: 496 B

View File

@ -0,0 +1,41 @@
<script setup lang="ts">
import { ref } from 'vue'
defineProps<{ msg: string }>()
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<button type="button" @click="count++">count is {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test HMR
</p>
</div>
<p>
Check out
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
>create-vue</a
>, the official Vue + Vite starter
</p>
<p>
Learn more about IDE Support for Vue in the
<a
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
target="_blank"
>Vue Docs Scaling up Guide</a
>.
</p>
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template>
<style scoped>
.read-the-docs {
color: #888;
}
</style>

12
apps/play/src/main.ts Normal file
View File

@ -0,0 +1,12 @@
import { createApp } from '@penna/vue'
import TestModule from './module'
import '@unocss/reset/tailwind.css'
import 'uno.css'
createApp({
modules: [
TestModule,
],
}).then(() => {
// TODO
})

15
apps/play/src/module.ts Normal file
View File

@ -0,0 +1,15 @@
import { defineModule } from '@penna/vue'
export default defineModule({
// 静态路由配置
routes: [
{
path: '/home',
component: () => import('./pages/home/index.vue'),
meta: {
title: '首页',
},
},
],
pages: {},
})

View File

@ -0,0 +1,13 @@
<script setup lang="ts">
</script>
<template>
<div>
Home
</div>
</template>
<style scoped>
</style>

1
apps/play/src/vite-env.d.ts vendored Normal file
View File

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

View File

@ -0,0 +1,23 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"baseUrl": ".",
"paths": {
"@penna/vue": ["../../packages/vue/src/index.ts"]
},
/* Linting */
"strict": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noUncheckedSideEffectImports": true
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue"
]
}

7
apps/play/tsconfig.json Normal file
View File

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

View File

@ -0,0 +1,27 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2023",
"lib": ["ES2023"],
"moduleDetection": "force",
"module": "ESNext",
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
/* Linting */
"strict": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noEmit": true,
"verbatimModuleSyntax": true,
"erasableSyntaxOnly": true,
"skipLibCheck": true,
"noUncheckedSideEffectImports": true
},
"include": [
"vite.config.ts"
]
}

28
apps/play/uno.config.ts Normal file
View File

@ -0,0 +1,28 @@
import {
defineConfig,
presetAttributify,
presetIcons,
presetTypography,
presetWind3,
transformerDirectives,
transformerVariantGroup,
} from 'unocss'
export default defineConfig({
shortcuts: [
],
theme: {
},
presets: [
presetWind3(),
presetAttributify(),
presetIcons({
scale: 1.2,
}),
presetTypography(),
],
transformers: [
transformerDirectives(),
transformerVariantGroup(),
],
})

22
apps/play/vite.config.ts Normal file
View File

@ -0,0 +1,22 @@
import * as path from 'node:path'
import { fileURLToPath } from 'node:url'
import vue from '@vitejs/plugin-vue'
import unocss from 'unocss/vite'
import { defineConfig } from 'vite'
const baseUrl = fileURLToPath(new URL('./', import.meta.url))
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
unocss(),
],
resolve: {
alias: [
{
find: /^@penna\/vue/,
replacement: path.resolve(baseUrl, '../../packages/vue/src'),
},
],
},
})

9
eslint.config.ts Normal file
View File

@ -0,0 +1,9 @@
import antfu from '@antfu/eslint-config'
export default antfu({
vue: true,
typescript: true,
rules: {
},
})

19
package.json Normal file
View File

@ -0,0 +1,19 @@
{
"name": "penna-app",
"type": "module",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "pnpm -F play dev"
},
"devDependencies": {
"@antfu/eslint-config": "^5.2.1",
"@types/node": "^24.3.0",
"@vue/tsconfig": "^0.7.0",
"eslint": "^9.34.0",
"jiti": "^2.5.1",
"tsdown": "^0.14.2",
"typescript": "~5.8.3",
"vite": "^7.1.2"
}
}

View File

@ -0,0 +1,18 @@
{
"name": "@penna/core",
"type": "module",
"version": "1.0.0",
"description": "this is penna core",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
},
"dependencies": {
},
"devDependencies": {
"tsdown": "^0.14.2",
"vite": "^7.1.3"
}
}

View File

View File

@ -0,0 +1,11 @@
{
"extends": [
"../../tsconfig.json"
],
"compilerOptions": {
},
"include": [
"src/**/*.ts"
]
}

23
packages/vue/package.json Normal file
View File

@ -0,0 +1,23 @@
{
"name": "@penna/vue",
"type": "module",
"version": "1.0.0-beta.1",
"description": "this is penna core",
"publishConfig": {
"access": "public"
},
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "tsdown",
"prepublish": "pnpm build"
},
"dependencies": {
"pinia": "^3.0.3",
"vue": "^3.5.20",
"vue-router": "^4.5.1"
}
}

View File

@ -0,0 +1,96 @@
import type { RouteRecordRaw, RouterOptions } from 'vue-router'
import type { ModuleOptions } from './modules.ts'
import { createApp as createClientApp, defineComponent, h } from 'vue'
import { createRouter, createWebHistory, RouterView } from 'vue-router'
import { useModules } from './utils/module.ts'
export interface CreateAppOptions {
/**
* 根容器选择器
* @default "#app"
*/
rootContainer?: string
/**
* 模块配置
*/
modules?: ModuleOptions[]
/**
* 路由信息配置项
* @default {}
*/
routerOptions?: RouterOptions
/**
* 根路由重定向地址
* @default "/home"
*/
rootRedirect?: string
}
const RootComponent = defineComponent(
(_) => {
// 获取全局配置中的信息
const { modules } = useModules()
for (const module of modules) {
if (module.setup) {
module.setup?.()
}
}
return () => {
return h(RouterView)
}
},
)
export async function createApp(options: CreateAppOptions) {
const app = createClientApp(RootComponent)
const modules = options?.modules ?? []
// 挂载到全局的配置中
app.config.globalProperties.$modules = modules
let routes = (options?.routerOptions?.routes ?? []) as RouteRecordRaw[]
// 1. 先执行所有模块的setup方法
for (const module of modules) {
// 顺序执行以避免问题
if (module.app) {
// 支持异步
await module.app(app)
}
// 是否存在静态的路由配置信息
if (module.routes) {
if (typeof module.routes === 'function') {
routes = module.routes(routes) ?? routes
}
else {
routes.push(...(module.routes ?? []))
}
}
}
let rootRedirect = options?.rootRedirect ?? '/home'
if (rootRedirect === '/') {
console.error('[createApp]: rootRedirect cannot be \'/\', fall back to \'/home\'')
rootRedirect = '/home'
}
const rootRoute: RouteRecordRaw = {
path: '',
name: 'ROOT_ROUTE',
redirect: rootRedirect,
children: routes,
}
const router = createRouter({
history: createWebHistory(),
routes: [
rootRoute,
],
...options?.routerOptions,
})
// 2. 再执行所有模块的router方法
for (const module of modules) {
if (module.router) {
// 执行router相关的配置
await module.router(router)
}
}
app.use(router)
// 判断是否已经完成了router的加载
await router.isReady()
app.mount(options?.rootContainer ?? '#app')
}

View File

@ -0,0 +1,3 @@
export { createApp } from './create-app'
export type { ModuleOptions } from './modules'
export { defineModule } from './modules'

View File

@ -0,0 +1,36 @@
import type { App } from 'vue'
import type { Router, RouteRecordRaw } from 'vue-router'
export interface PageType {
component: any
meta?: RouteRecordRaw['meta']
name?: string
}
export interface ModuleOptions {
/**
* 初始化模块配置
* @param app
*/
app?: (app: App) => Promise<void> | void
/**
* 静态的路由配置信息
*/
routes?: RouteRecordRaw[] | ((routes: RouteRecordRaw[]) => RouteRecordRaw[])
/**
* 动态路由页面配置
*/
pages?: Record<string, PageType>
/**
* 给用户router的实例进行操作
*/
router?: (router: Router) => Promise<void> | void
/**
* 这里的setup就是rootComponent的setup
*/
setup?: () => void
}
export function defineModule(options: ModuleOptions) {
return options
}

View File

@ -0,0 +1,13 @@
import type { ModuleOptions } from '../modules.ts'
import { getCurrentInstance } from 'vue'
export function useModules() {
const instance = getCurrentInstance()
if (!instance) {
throw new Error('Failed to get current instance')
}
const modules = (instance.appContext.config.globalProperties?.$modules ?? []) as ModuleOptions[]
return {
modules,
}
}

View File

@ -0,0 +1,11 @@
{
"extends": [
"../../tsconfig.json"
],
"compilerOptions": {
},
"include": [
"src/**/*.ts"
]
}

View File

@ -0,0 +1,9 @@
import { defineConfig } from 'tsdown'
export default defineConfig({
platform: 'browser',
entry: [
'./src/index.ts',
],
dts: true,
})

4632
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

3
pnpm-workspace.yaml Normal file
View File

@ -0,0 +1,3 @@
packages:
- 'packages/*'
- 'apps/*'

6
tsconfig.json Normal file
View File

@ -0,0 +1,6 @@
{
"extends": "@vue/tsconfig/tsconfig.json",
"compilerOptions": {
}
}