Skip to content

Commit e90aca5

Browse files
committed
feat(core,core-react,react,source-react): add React package support
1 parent 1fac03e commit e90aca5

41 files changed

Lines changed: 2138 additions & 52 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,12 @@
5757
"@unocss/eslint-config": "^66.6.8",
5858
"@unocss/eslint-plugin": "^66.6.8",
5959
"@unocss/preset-mini": "^66.6.8",
60+
"@vitest/browser-playwright": "4.1.4",
6061
"bumpp": "^11.0.1",
6162
"changelogithub": "^14.0.0",
6263
"eslint": "^10.2.0",
6364
"eslint-plugin-format": "^2.0.1",
65+
"playwright": "^1.60.0",
6466
"taze": "^19.11.0",
6567
"tsdown": "^0.21.8",
6668
"tsx": "^4.21.0",

packages/core-react/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# @velin-dev/core-react
2+
3+
Core React utilities for Velin.
4+
5+
## Usage
6+
7+
```tsx
8+
import { renderComponent } from '@velin-dev/core-react'
9+
10+
function Prompt({ name }: { name: string }) {
11+
return (
12+
<article>
13+
<h1>{`Hello ${name}`}</h1>
14+
<p>Render React components as Markdown prompts.</p>
15+
</article>
16+
)
17+
}
18+
19+
const markdown = await renderComponent(Prompt, { name: 'Velin' })
20+
21+
console.log(markdown)
22+
```

packages/core-react/package.json

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
{
2+
"name": "@velin-dev/core-react",
3+
"type": "module",
4+
"version": "0.4.1",
5+
"description": "Develop prompts with Vue SFC or Markdown like pro.",
6+
"author": {
7+
"name": "RainbowBird",
8+
"email": "rbxin2003@outlook.com",
9+
"url": "https://github.com/luoling8192"
10+
},
11+
"contributors": [
12+
{
13+
"name": "Neko Ayaka",
14+
"email": "neko@ayaka.moe",
15+
"url": "https://github.com/nekomeowww"
16+
}
17+
],
18+
"license": "MIT",
19+
"homepage": "https://github.com/moeru-ai/velin",
20+
"repository": {
21+
"type": "git",
22+
"url": "git+https://github.com/moeru-ai/velin.git",
23+
"directory": "packages/core-react"
24+
},
25+
"bugs": "https://github.com/moeru-ai/velin/issues",
26+
"exports": {
27+
".": {
28+
"types": "./dist/index.d.mts",
29+
"import": "./dist/index.mjs"
30+
},
31+
"./render-browser": {
32+
"types": "./dist/render-browser/index.d.mts",
33+
"import": "./dist/render-browser/index.mjs"
34+
},
35+
"./render-node": {
36+
"types": "./dist/render-node/index.d.mts",
37+
"import": "./dist/render-node/index.mjs"
38+
},
39+
"./render-shared": {
40+
"types": "./dist/render-shared/index.d.mts",
41+
"import": "./dist/render-shared/index.mjs"
42+
}
43+
},
44+
"main": "./dist/index.mjs",
45+
"module": "./dist/index.mjs",
46+
"types": "./dist/index.d.mts",
47+
"files": [
48+
"README.md",
49+
"dist",
50+
"package.json"
51+
],
52+
"scripts": {
53+
"dev": "tsdown",
54+
"stub": "tsdown",
55+
"build": "tsdown",
56+
"typecheck": "tsc --noEmit",
57+
"attw": "attw --pack . --profile esm-only --ignore-rules cjs-resolves-to-esm"
58+
},
59+
"peerDependencies": {
60+
"react": "^19.2.1",
61+
"react-dom": "^19.2.1"
62+
},
63+
"dependencies": {
64+
"@velin-dev/utils": "workspace:^"
65+
},
66+
"devDependencies": {
67+
"@types/react": "^19.2.7",
68+
"@types/react-dom": "^19.2.3",
69+
"react": "^19.2.1",
70+
"react-dom": "^19.2.1"
71+
}
72+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { describe, expect, it } from 'vitest'
2+
3+
import { renderComponent, renderElement } from './index'
4+
5+
describe('renderElement from default entry in browser', () => {
6+
it('renders a React element to Markdown', async () => {
7+
await expect(renderElement(<span>Browser element</span>)).resolves.toBe('Browser element\n')
8+
})
9+
})
10+
11+
describe('renderComponent from default entry in browser', () => {
12+
it('renders function component output to Markdown', async () => {
13+
function Prompt() {
14+
return <div>Hello browser</div>
15+
}
16+
17+
await expect(renderComponent(Prompt)).resolves.toBe('Hello browser\n')
18+
})
19+
})
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { describe, expect, it } from 'vitest'
2+
3+
import { renderComponent, renderElement } from './index'
4+
5+
describe('renderComponent from default entry', () => {
6+
it('should type-check props requirements', () => {
7+
function Prompt() {
8+
return <div>Hello Velin</div>
9+
}
10+
11+
function RequiredPrompt({ name }: { name: string }) {
12+
return <div>{`Hello ${name}`}</div>
13+
}
14+
15+
void renderComponent(Prompt)
16+
// @ts-expect-error required props must be provided
17+
void renderComponent(RequiredPrompt)
18+
void renderComponent(RequiredPrompt, { name: 'React' })
19+
})
20+
21+
it('should render function component output to Markdown', async () => {
22+
function Prompt() {
23+
return <div>Hello Velin</div>
24+
}
25+
26+
await expect(renderComponent(Prompt)).resolves.toBe('Hello Velin\n')
27+
})
28+
})
29+
30+
describe('renderElement from default entry', () => {
31+
it('should render a React element to Markdown', async () => {
32+
await expect(renderElement(<span>Element prompt</span>)).resolves.toBe('Element prompt\n')
33+
})
34+
})

packages/core-react/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { renderComponent, renderElement } from './render-shared'
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { describe, expect, it } from 'vitest'
2+
3+
import { ImportedPrompt } from '../testdata/imported-prompt'
4+
import { renderComponent, renderElement } from './index'
5+
6+
describe('renderElement from render-browser', () => {
7+
it('renders a React element to Markdown', async () => {
8+
await expect(renderElement(<span>Browser element</span>)).resolves.toBe('Browser element\n')
9+
})
10+
})
11+
12+
describe('renderComponent from render-browser', () => {
13+
it('renders function component output to Markdown', async () => {
14+
function Prompt() {
15+
return <div>Hello browser</div>
16+
}
17+
18+
await expect(renderComponent(Prompt)).resolves.toBe('Hello browser\n')
19+
})
20+
21+
it('converts nested HTML to Markdown', async () => {
22+
function Prompt() {
23+
return (
24+
<section>
25+
<h1>Browser Plan</h1>
26+
<ul>
27+
<li>Render</li>
28+
<li>Convert</li>
29+
</ul>
30+
</section>
31+
)
32+
}
33+
34+
const result = await renderComponent(Prompt)
35+
36+
expect(result).toContain('# Browser Plan')
37+
expect(result).toContain('- Render')
38+
expect(result).toContain('- Convert')
39+
})
40+
41+
it('renders an imported component with React imports', async () => {
42+
await expect(renderComponent(ImportedPrompt, { name: 'browser import' })).resolves.toBe(
43+
'# Imported\n\n**BROWSER IMPORT**\n',
44+
)
45+
})
46+
})
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { renderComponent, renderElement } from '../render-shared'
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { describe, expect, it } from 'vitest'
2+
3+
import { ImportedPrompt } from '../testdata/imported-prompt'
4+
import { renderComponent, renderElement } from './index'
5+
6+
describe('renderComponent from render-node', () => {
7+
it('should type-check props requirements', () => {
8+
function Prompt() {
9+
return <div>Hello Velin</div>
10+
}
11+
12+
function RequiredPrompt({ name }: { name: string }) {
13+
return <div>{`Hello ${name}`}</div>
14+
}
15+
16+
void renderComponent(Prompt)
17+
// @ts-expect-error required props must be provided
18+
void renderComponent(RequiredPrompt)
19+
void renderComponent(RequiredPrompt, { name: 'React' })
20+
})
21+
22+
it('should render function component output to Markdown', async () => {
23+
function Prompt() {
24+
return <div>Hello Velin</div>
25+
}
26+
27+
await expect(renderComponent(Prompt)).resolves.toBe('Hello Velin\n')
28+
})
29+
30+
it('should pass props to the component', async () => {
31+
function Prompt({ name }: { name: string }) {
32+
return <div>{`Hello ${name}`}</div>
33+
}
34+
35+
await expect(renderComponent(Prompt, { name: 'React' })).resolves.toBe('Hello React\n')
36+
})
37+
38+
it('should convert nested HTML to Markdown', async () => {
39+
function Prompt() {
40+
return (
41+
<section>
42+
<h1>Plan</h1>
43+
<ul>
44+
<li>Render</li>
45+
<li>Convert</li>
46+
</ul>
47+
</section>
48+
)
49+
}
50+
51+
const result = await renderComponent(Prompt)
52+
53+
expect(result).toContain('# Plan')
54+
expect(result).toContain('- Render')
55+
expect(result).toContain('- Convert')
56+
})
57+
58+
it('should render null output to empty Markdown', async () => {
59+
function Prompt() {
60+
return null
61+
}
62+
63+
await expect(renderComponent(Prompt)).resolves.toBe('')
64+
})
65+
66+
it('should render an imported component with React imports', async () => {
67+
await expect(renderComponent(ImportedPrompt, { name: 'node import' })).resolves.toBe(
68+
'# Imported\n\n**NODE IMPORT**\n',
69+
)
70+
})
71+
})
72+
73+
describe('renderElement from render-node', () => {
74+
it('should render a React element to Markdown', async () => {
75+
await expect(renderElement(<span>Element prompt</span>)).resolves.toBe('Element prompt\n')
76+
})
77+
})
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { renderComponent, renderElement } from '../render-shared'

0 commit comments

Comments
 (0)