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 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)