Skip to content

Commit 0e4f713

Browse files
Merge pull request #90 from uniocjs/dev-groupguanfang
feat: add unioc reflector (#89)
2 parents 18ba20f + 399b8e2 commit 0e4f713

24 files changed

+2405
-113
lines changed

.changeset/cruel-ants-draw.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@unioc/reflector": patch
3+
---
4+
5+
feat: add unioc reflector (#89)

fixtures/reflector/index.html

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<link rel="icon" href="/favicon.ico" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
</head>
8+
<body>
9+
<div id="app"></div>
10+
<a href="/__inspect/#/module?id=./main.ts&index=2" target="_blank" style="text-decoration: none; margin-top:10px; display: block;">visit /__inspect/ to inspect the intermediate state</a>
11+
<script type="module" src="./main.ts"></script>
12+
</body>
13+
</html>

fixtures/reflector/main.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/* eslint-disable no-console */
2+
import './test'
3+
import './polyfill'
4+
5+
export class Hello {
6+
constructor(_a: string, _b: Array<string>) {}
7+
8+
hello: string = 'world'
9+
10+
async helloWorld(_a: string): Promise<void> {}
11+
}
12+
13+
console.log(Hello[Symbol.metadata])

fixtures/reflector/package.json

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "fixture-reflector",
3+
"type": "module",
4+
"private": true,
5+
"scripts": {
6+
"dev": "nodemon -w '../packages/core/reflector/src/**/*.ts' -e .ts -x vite"
7+
},
8+
"devDependencies": {
9+
"nodemon": "^3.1.9",
10+
"vite": "^6.1.0",
11+
"vite-plugin-inspect": "^10.1.1"
12+
}
13+
}

fixtures/reflector/polyfill.js

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
Reflect._uniocMetadata = (metadataKey, metadataValue) => {
2+
/**
3+
* @param _target - The target object.
4+
* @param {DecoratorContext} ctx
5+
*/
6+
return (_target, ctx) => {
7+
if (!ctx.metadata)
8+
ctx.metadata = {}
9+
if (!('unioc' in ctx.metadata) || typeof ctx.metadata.unioc !== 'object')
10+
ctx.metadata.unioc = {}
11+
if (!('reflect' in ctx.metadata.unioc) || typeof ctx.metadata.unioc.reflect !== 'object')
12+
ctx.metadata.unioc.reflect = {}
13+
if (!('members' in ctx.metadata.unioc.reflect))
14+
ctx.metadata.unioc.reflect.members = []
15+
16+
if (ctx.kind === 'class') {
17+
ctx.metadata.unioc.reflect[metadataKey] = metadataValue
18+
}
19+
else {
20+
const foundMemberIndex = ctx.metadata.unioc.reflect.members.findIndex(member => member.name === ctx.name)
21+
22+
if (foundMemberIndex === -1) {
23+
ctx.metadata.unioc.reflect.members.push({
24+
name: ctx.name,
25+
[metadataKey]: metadataValue,
26+
})
27+
}
28+
else {
29+
ctx.metadata.unioc.reflect.members[foundMemberIndex][metadataKey] = metadataValue
30+
}
31+
}
32+
}
33+
}
34+
35+
export {}

fixtures/reflector/test.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
if (!Symbol.metadata)
2+
// eslint-disable-next-line ts/ban-ts-comment
3+
// @ts-expect-error
4+
Symbol.metadata = 'reflect-metadata'

fixtures/reflector/tsconfig.json

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2019",
4+
"jsx": "preserve",
5+
"lib": ["ESNext"],
6+
"module": "ESNext",
7+
"moduleResolution": "bundler",
8+
"strict": true,
9+
"skipLibCheck": true
10+
},
11+
"include": ["./**/*"]
12+
}

fixtures/reflector/vite.config.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { defineConfig } from 'vite'
2+
import Inspect from 'vite-plugin-inspect'
3+
import Unplugin from '../../packages/core/reflector/src/vite'
4+
5+
export default defineConfig({
6+
plugins: [
7+
Inspect(),
8+
Unplugin(),
9+
],
10+
})

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
},
1212
"scripts": {
1313
"dev:nestjs": "pnpm -F fixture-nestjs dev",
14+
"dev:reflector": "pnpm -F fixture-reflector dev",
1415
"lint": "eslint .",
1516
"postinstall": "npx simple-git-hooks",
1617
"test": "vitest",

packages/core/reflector/package.json

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
{
2+
"name": "@unioc/reflector",
3+
"type": "module",
4+
"version": "0.0.5-beta.1",
5+
"description": "Reflector unplugin for unioc.",
6+
"author": "Naily Zero <[email protected]> (https://naily.cc)",
7+
"license": "MIT",
8+
"homepage": "https://unioc.dev",
9+
"repository": {
10+
"type": "git",
11+
"url": "https://github.com/uniocjs/core.git",
12+
"directory": "packages/core/reflector"
13+
},
14+
"bugs": {
15+
"url": "https://github.com/uniocjs/core/issues"
16+
},
17+
"keywords": [
18+
"unioc",
19+
"reflect",
20+
"ioc",
21+
"dependency inversion",
22+
"dependency injection",
23+
"di",
24+
"inversion of control container"
25+
],
26+
"exports": {
27+
".": {
28+
"types": "./dist/index.d.ts",
29+
"import": "./dist/index.js",
30+
"require": "./dist/index.cjs"
31+
},
32+
"./astro": {
33+
"import": "./dist/astro.js",
34+
"require": "./dist/astro.cjs"
35+
},
36+
"./rspack": {
37+
"import": "./dist/rspack.js",
38+
"require": "./dist/rspack.cjs"
39+
},
40+
"./vite": {
41+
"import": "./dist/vite.js",
42+
"require": "./dist/vite.cjs"
43+
},
44+
"./webpack": {
45+
"import": "./dist/webpack.js",
46+
"require": "./dist/webpack.cjs"
47+
},
48+
"./rollup": {
49+
"import": "./dist/rollup.js",
50+
"require": "./dist/rollup.cjs"
51+
},
52+
"./esbuild": {
53+
"import": "./dist/esbuild.js",
54+
"require": "./dist/esbuild.cjs"
55+
},
56+
"./nuxt": {
57+
"import": "./dist/nuxt.js",
58+
"require": "./dist/nuxt.cjs"
59+
},
60+
"./farm": {
61+
"import": "./dist/farm.js",
62+
"require": "./dist/farm.cjs"
63+
},
64+
"./types": {
65+
"import": "./dist/types.js",
66+
"require": "./dist/types.cjs"
67+
},
68+
"./*": "./*"
69+
},
70+
"main": "dist/index.cjs",
71+
"module": "dist/index.js",
72+
"types": "dist/index.d.ts",
73+
"typesVersions": {
74+
"*": {
75+
"*": [
76+
"./dist/*",
77+
"./*"
78+
]
79+
}
80+
},
81+
"files": [
82+
"dist"
83+
],
84+
"scripts": {
85+
"build": "tsdown",
86+
"watch": "tsdown -w",
87+
"prepublishOnly": "tsdown"
88+
},
89+
"publishConfig": {
90+
"access": "public"
91+
},
92+
"peerDependencies": {
93+
"@farmfe/core": ">=1",
94+
"@nuxt/kit": "^3",
95+
"@nuxt/schema": "^3",
96+
"esbuild": "*",
97+
"rollup": "^3",
98+
"vite": ">=3",
99+
"webpack": "^4 || ^5"
100+
},
101+
"peerDependenciesMeta": {
102+
"@farmfe/core": {
103+
"optional": true
104+
},
105+
"@nuxt/kit": {
106+
"optional": true
107+
},
108+
"@nuxt/schema": {
109+
"optional": true
110+
},
111+
"esbuild": {
112+
"optional": true
113+
},
114+
"rollup": {
115+
"optional": true
116+
},
117+
"vite": {
118+
"optional": true
119+
},
120+
"webpack": {
121+
"optional": true
122+
}
123+
},
124+
"dependencies": {
125+
"@rollup/pluginutils": "^5.1.4",
126+
"magic-string": "^0.30.17",
127+
"oxc-parser": "^0.63.0",
128+
"unplugin": "^2.3.2"
129+
},
130+
"devDependencies": {
131+
"@nuxt/kit": "^3.16.2",
132+
"@nuxt/schema": "^3.16.2",
133+
"webpack": "^5.99.5"
134+
}
135+
}

packages/core/reflector/src/astro.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { Options } from './types'
2+
3+
import unplugin from '.'
4+
5+
export default (options: Options): any => ({
6+
name: 'unioc:reflector',
7+
hooks: {
8+
'astro:config:setup': async (astro: any) => {
9+
astro.config.vite.plugins ||= []
10+
astro.config.vite.plugins.push(unplugin.vite(options))
11+
},
12+
},
13+
})
+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import type { Class, ClassElement, MethodDefinition, ParseResult, TSType, TSTypeName } from 'oxc-parser'
2+
import MagicString from 'magic-string'
3+
import { walk } from './walk'
4+
5+
type ConstructorType = `Object` | `Array` | `String` | `Number` | `Boolean` | `Function` | (string & {})
6+
7+
function transformTSTypeName(typeName: TSTypeName): ConstructorType {
8+
if (typeName.type === 'Identifier') {
9+
return typeName.typeAnnotation ? transformTSType(typeName.typeAnnotation) : `(() => { try { return ${typeName.name} } catch (e) { return Object } })()`
10+
}
11+
return transformTSTypeName(typeName.left)
12+
}
13+
14+
function transformTSType(type: TSType): ConstructorType {
15+
switch (type.type) {
16+
case 'TSStringKeyword':
17+
return 'String'
18+
case 'TSNumberKeyword':
19+
return 'Number'
20+
case 'TSBooleanKeyword':
21+
return 'Boolean'
22+
case 'TSFunctionType':
23+
return 'Function'
24+
case 'TSArrayType':
25+
return 'Array'
26+
case 'TSTypeReference':
27+
return transformTSTypeName(type.typeName)
28+
default:
29+
return 'Object'
30+
}
31+
}
32+
33+
function transformClassMembers(members: ClassElement[], s: MagicString): void {
34+
for (const member of members) {
35+
if (member.type === 'StaticBlock')
36+
continue
37+
if (member.type === 'TSAbstractMethodDefinition')
38+
continue
39+
if (member.type === 'TSIndexSignature')
40+
continue
41+
if (member.type === 'TSAbstractPropertyDefinition')
42+
continue
43+
if (member.type === 'TSAbstractAccessorProperty')
44+
continue
45+
46+
if (member.type === 'PropertyDefinition') {
47+
const memberDecoratorStart = member.decorators?.[0]?.start || member.start
48+
s.appendLeft(memberDecoratorStart, `@Reflect._uniocMetadata("design:type", ${member.typeAnnotation?.typeAnnotation ? transformTSType(member.typeAnnotation.typeAnnotation) : 'Object'})\n`)
49+
}
50+
else if (member.type === 'MethodDefinition') {
51+
if (member.kind === 'constructor')
52+
continue
53+
54+
const memberDecoratorStart = member.decorators?.[0]?.start || member.start
55+
const returnType = member.value.returnType?.typeAnnotation ? transformTSType(member.value.returnType.typeAnnotation) : 'Object'
56+
const paramTypes = member.value.params.map(param => param.typeAnnotation?.typeAnnotation ? transformTSType(param.typeAnnotation.typeAnnotation) : 'Object').join(',')
57+
s.appendLeft(memberDecoratorStart, `@Reflect._uniocMetadata("design:type", Function)\n@Reflect._uniocMetadata("design:returntype", ${returnType})\n@Reflect._uniocMetadata("design:paramtypes", [${paramTypes}])\n`)
58+
}
59+
}
60+
}
61+
62+
function transformClassDeclaration(node: Class, s: MagicString): void {
63+
const decoratorStart = node.decorators?.[0]?.start || node.start
64+
const constructorMethod = node.body.body.find(v => v.type === 'MethodDefinition' && v.kind === 'constructor') as MethodDefinition | undefined
65+
const paramTypes = constructorMethod?.value.params
66+
.map(param => param.typeAnnotation?.typeAnnotation)
67+
.filter(Boolean)
68+
.map(value => transformTSType(value!)) || []
69+
70+
s.appendLeft(decoratorStart, `@Reflect._uniocMetadata("design:type", Function)\n@Reflect._uniocMetadata("design:paramtypes", [${paramTypes.join(',')}])\n`)
71+
}
72+
73+
export async function transform(ast: ParseResult, fullText: string): Promise<MagicString> {
74+
const s = new MagicString(fullText)
75+
76+
walk(ast.program, {
77+
all(node) {
78+
if (node.type !== 'ClassDeclaration')
79+
return
80+
81+
transformClassDeclaration(node, s)
82+
transformClassMembers(node.body.body, s)
83+
},
84+
})
85+
86+
return s
87+
}

0 commit comments

Comments
 (0)