vue-to-tsx 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +232 -0
- package/dist/cli.js +1967 -0
- package/dist/index.js +1750 -0
- package/package.json +61 -0
- package/src/cli.ts +274 -0
- package/src/index.ts +94 -0
- package/src/llm/index.ts +138 -0
- package/src/parser.ts +40 -0
- package/src/playground/index.html +122 -0
- package/src/playground/server.ts +57 -0
- package/src/script/auto-imports.ts +103 -0
- package/src/script/imports.ts +109 -0
- package/src/script/index.ts +305 -0
- package/src/script/macros.ts +528 -0
- package/src/style/index.ts +83 -0
- package/src/template/attributes.ts +273 -0
- package/src/template/control-flow.ts +187 -0
- package/src/template/directives.ts +81 -0
- package/src/template/elements.ts +135 -0
- package/src/template/events.ts +100 -0
- package/src/template/index.ts +100 -0
- package/src/template/slots.ts +175 -0
- package/src/template/utils.ts +146 -0
- package/src/template/walker.ts +290 -0
- package/src/types.ts +163 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Nikhil Verma
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# vue-to-tsx
|
|
2
|
+
|
|
3
|
+
Convert Vue Single File Components (`.vue`) to Vue TSX (`.tsx` + `.module.css`).
|
|
4
|
+
|
|
5
|
+
This is **not** a React migration tool. The output is Vue TSX -- it stays within the Vue ecosystem, using `defineComponent`, Vue's JSX transform, and CSS modules.
|
|
6
|
+
|
|
7
|
+
## Why?
|
|
8
|
+
|
|
9
|
+
Vue's Single File Component format is a well-designed authoring experience. But it comes with a cost: `.vue` files only work with custom tooling. Your editor needs a Vue-specific extension. Your bundler needs a Vue plugin. Your linter, your test runner, your CI -- everything in the chain needs to know what a `.vue` file is.
|
|
10
|
+
|
|
11
|
+
TSX changes that. A `.tsx` file is just TypeScript. It works everywhere TypeScript works -- no plugins, no extensions, no custom language servers. You get:
|
|
12
|
+
|
|
13
|
+
- **Native TypeScript support** -- full type checking, refactoring, and go-to-definition without Volar or any editor extension
|
|
14
|
+
- **Standard tooling** -- any bundler, linter, test runner, or CI pipeline that supports TypeScript works out of the box
|
|
15
|
+
- **All of Vue's power** -- `defineComponent`, `ref`, `computed`, `watch`, slots, emits, provide/inject -- it all works in TSX
|
|
16
|
+
- **Full Nuxt compatibility** -- Nuxt auto-imports, composables, and middleware work identically in `.tsx` files
|
|
17
|
+
- **Better composition** -- components are just functions returning JSX, making it natural to compose, split, and reuse rendering logic
|
|
18
|
+
- **No lock-in** -- your code is portable TypeScript, not a framework-specific file format
|
|
19
|
+
|
|
20
|
+
vue-to-tsx automates the conversion so you can migrate gradually, file by file, without rewriting anything by hand.
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
- Template to JSX conversion (v-if/v-for/v-show/v-model, slots, events)
|
|
25
|
+
- `<script setup>` to `defineComponent` with full macro support (defineProps, defineEmits, defineSlots, defineExpose, defineOptions, defineModel)
|
|
26
|
+
- Scoped CSS to CSS modules (`.module.css`)
|
|
27
|
+
- Handles complex patterns: v-if/v-else-if/v-else chains, dynamic components, named/scoped slots
|
|
28
|
+
- Optional LLM fallback for patterns that can't be converted deterministically (Anthropic and OpenAI)
|
|
29
|
+
- CLI for batch conversion and library API for programmatic use
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# bun
|
|
35
|
+
bun add -d vue-to-tsx
|
|
36
|
+
|
|
37
|
+
# npm
|
|
38
|
+
npm install -D vue-to-tsx
|
|
39
|
+
|
|
40
|
+
# pnpm
|
|
41
|
+
pnpm add -D vue-to-tsx
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## CLI usage
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Convert a single file
|
|
48
|
+
vue-to-tsx src/components/MyComponent.vue
|
|
49
|
+
|
|
50
|
+
# Convert a directory recursively
|
|
51
|
+
vue-to-tsx src/components/
|
|
52
|
+
|
|
53
|
+
# Convert and delete original .vue files (in-place replacement)
|
|
54
|
+
vue-to-tsx src/components/ --delete
|
|
55
|
+
|
|
56
|
+
# Convert with LLM fallback for complex patterns
|
|
57
|
+
vue-to-tsx src/components/ --llm
|
|
58
|
+
|
|
59
|
+
# Write output to a specific directory
|
|
60
|
+
vue-to-tsx src/components/ --out-dir converted/
|
|
61
|
+
|
|
62
|
+
# Preview what would happen without writing anything
|
|
63
|
+
vue-to-tsx src/components/ --dry-run --delete
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Library API
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { convert } from 'vue-to-tsx';
|
|
70
|
+
|
|
71
|
+
const source = `
|
|
72
|
+
<template>
|
|
73
|
+
<div class="container">
|
|
74
|
+
<h1>{{ title }}</h1>
|
|
75
|
+
<button @click="handleClick">Click me</button>
|
|
76
|
+
</div>
|
|
77
|
+
</template>
|
|
78
|
+
|
|
79
|
+
<script setup lang="ts">
|
|
80
|
+
const props = defineProps<{ title: string }>();
|
|
81
|
+
const emit = defineEmits<{ click: [] }>();
|
|
82
|
+
|
|
83
|
+
function handleClick() {
|
|
84
|
+
emit('click');
|
|
85
|
+
}
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
<style scoped>
|
|
89
|
+
.container {
|
|
90
|
+
padding: 16px;
|
|
91
|
+
}
|
|
92
|
+
</style>
|
|
93
|
+
`;
|
|
94
|
+
|
|
95
|
+
const result = await convert(source, {
|
|
96
|
+
componentName: 'MyComponent',
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
console.log(result.tsx); // The generated .tsx file
|
|
100
|
+
console.log(result.css); // The generated .module.css file (or null)
|
|
101
|
+
console.log(result.warnings); // Any conversion warnings
|
|
102
|
+
console.log(result.fallbacks); // Items that need manual review
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## How it works
|
|
106
|
+
|
|
107
|
+
1. **Template to JSX** -- The Vue template AST (from `@vue/compiler-sfc`) is walked and converted to JSX. Directives like `v-if` become ternary expressions, `v-for` becomes `.map()`, `@click` becomes `onClick`, etc.
|
|
108
|
+
|
|
109
|
+
2. **Script setup to defineComponent** -- `<script setup>` macros (`defineProps`, `defineEmits`, `defineSlots`, etc.) are extracted and rewritten into a `defineComponent` call with proper `setup()` function.
|
|
110
|
+
|
|
111
|
+
3. **Scoped CSS to CSS modules** -- `<style scoped>` blocks are converted to `.module.css` files. Class references in the template are rewritten to use `styles.className` syntax.
|
|
112
|
+
|
|
113
|
+
4. **LLM fallback** -- When a template pattern can't be converted deterministically (e.g., complex custom directives), it's marked with a fallback comment. With `--llm` enabled, these are sent to an LLM for resolution.
|
|
114
|
+
|
|
115
|
+
## Output formatting
|
|
116
|
+
|
|
117
|
+
The converter produces syntactically valid TSX but does not format or prettify it. Run your project's formatter on the output files to match your codebase style:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
# Prettier
|
|
121
|
+
bunx prettier --write "src/**/*.tsx"
|
|
122
|
+
|
|
123
|
+
# Biome
|
|
124
|
+
bunx biome format --write "src/**/*.tsx"
|
|
125
|
+
|
|
126
|
+
# dprint
|
|
127
|
+
bunx dprint fmt "src/**/*.tsx"
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## LLM-powered fallback
|
|
131
|
+
|
|
132
|
+
The deterministic converter handles the vast majority of Vue patterns -- `v-if`/`v-for`/`v-show`, `v-model`, slots, events, macros, CSS modules, and more. But roughly 5% of real-world Vue code uses patterns that have no single correct JSX translation. For these, vue-to-tsx offers an AI-powered fallback that understands Vue semantics and produces idiomatic JSX.
|
|
133
|
+
|
|
134
|
+
### What triggers the fallback
|
|
135
|
+
|
|
136
|
+
- **Custom directives** -- `v-focus`, `v-tooltip`, `v-click-outside`, and any app-specific directives
|
|
137
|
+
- **`v-memo`** -- performance hint with no direct JSX equivalent
|
|
138
|
+
- **Complex slot forwarding** -- dynamically passing through `$slots` to child components
|
|
139
|
+
- **Dynamic components with complex `:is`** -- `<component :is="someCondition ? CompA : CompB" />` with non-trivial expressions
|
|
140
|
+
|
|
141
|
+
### Without `--llm`
|
|
142
|
+
|
|
143
|
+
These patterns are marked with a `// TODO: vue-to-tsx` comment so you can resolve them manually:
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
{/* TODO: vue-to-tsx - Custom directive "v-tooltip" cannot be converted deterministically */}
|
|
147
|
+
{/* Original: <span v-tooltip="helpText">Hover me</span> */}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### With `--llm`
|
|
151
|
+
|
|
152
|
+
The same pattern is intelligently converted, producing a working JSX equivalent:
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
<Tooltip text={helpText.value}>
|
|
156
|
+
<span>Hover me</span>
|
|
157
|
+
</Tooltip>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
All fallback items in a file are **batched into a single API call**, so even a file with multiple custom directives only makes one request. This keeps costs low and latency minimal.
|
|
161
|
+
|
|
162
|
+
### Setup
|
|
163
|
+
|
|
164
|
+
Set an API key for your preferred provider:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
# Anthropic (default if both are set)
|
|
168
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
169
|
+
|
|
170
|
+
# OpenAI
|
|
171
|
+
export OPENAI_API_KEY=sk-...
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
The provider is auto-detected from whichever API key is set. If both are set, Anthropic is preferred. You can override this with `VUE_TO_TSX_LLM_PROVIDER`:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
export VUE_TO_TSX_LLM_PROVIDER=openai # force OpenAI even if ANTHROPIC_API_KEY is set
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Then pass `--llm` to the CLI:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
vue-to-tsx src/components/ --llm
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Model override
|
|
187
|
+
|
|
188
|
+
Default models: `claude-sonnet-4-5` (Anthropic), `gpt-4o` (OpenAI). Override via CLI flag or env var:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
# CLI flag
|
|
192
|
+
vue-to-tsx src/components/ --llm --llm-model gpt-4o-mini
|
|
193
|
+
|
|
194
|
+
# Environment variable
|
|
195
|
+
export VUE_TO_TSX_LLM_MODEL=claude-haiku-4-5-20251001
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Programmatic usage
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
const result = await convert(source, {
|
|
202
|
+
componentName: 'MyComponent',
|
|
203
|
+
llm: true,
|
|
204
|
+
llmModel: 'claude-sonnet-4-5',
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Options
|
|
209
|
+
|
|
210
|
+
The `convert()` function accepts an options object:
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
interface ConvertOptions {
|
|
214
|
+
componentName?: string; // Component name (derived from filename if not provided)
|
|
215
|
+
llm?: boolean; // Enable LLM fallback (default: false)
|
|
216
|
+
llmModel?: string; // LLM model to use (auto-detected from provider)
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
| Option | CLI flag | Default | Description |
|
|
221
|
+
|--------|----------|---------|-------------|
|
|
222
|
+
| `componentName` | (from filename) | PascalCase of filename | Name used in `defineComponent` |
|
|
223
|
+
| `llm` | `--llm` | `false` | Enable AI-powered fallback for unconvertible patterns |
|
|
224
|
+
| `llmModel` | `--llm-model` | Auto (provider-dependent) | LLM model ID for fallback resolution |
|
|
225
|
+
|
|
226
|
+
## Contributing
|
|
227
|
+
|
|
228
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
229
|
+
|
|
230
|
+
## License
|
|
231
|
+
|
|
232
|
+
[MIT](./LICENSE)
|