Package Exports
- @heatsrc/vue-declassified
- @heatsrc/vue-declassified/dist/main.cjs.js
- @heatsrc/vue-declassified/dist/main.es.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@heatsrc/vue-declassified) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
Vue Declassified (vuedc)

npm | github | vuedc playground
Vue Class Components -> Vue 3 script setup
Vue Declassified is an opinionated tool that will format Vue class components (including the v8 RC package) to script setup. This project a fork and re-write of yoyo930021's vc2c which is focused more on Vue 2 -> Composition API using defineComponent
.
Opinionated decisions
These decisions are made arbitrarily, mostly for sanity and convenience. You get what you get and you don't get upset.
- No/Limited configuration
- There is a lot of different edge cases to test and adding configuration options tends to act as a multiplier for those cases.
- Will only support TS
- Won't support esoteric/redundant
@Component
/@Options
options- Will consider accepting PRs
- Will order files
script
->template
->style
- Will reference macros by arbitrary variables (see below)
- Will be formatted by prettier with default config
- exception
printWidth
increased to 100 characters
- exception
Usage
vuedc CLI (recommended)
You can call the CLI tool to convert a file directly from a terminal. For more information see the vuedc readme.
pnpm add -g @heatsrc/vuedc
# or
npm install -g @heatsrc/vuedc
# or
yarn add -g @heatsrc/vuedc
vuedc -i myVueComponent.vue -o myVueComponent.converted.vue
or run directly with hot loading
pnpm dlx vuedc -i myVueComponent.vue -o myVueComponent.converted.vue
# or
npx vuedc -i myVueComponent.vue -o myVueComponent.converted.vue
# or
yarn dlx vuedc -i myVueComponent.vue -o myVueComponent.converted.vue
Programmatically
Install
Dependencies
If you want to use this without the cli tool you'll need to ensure you have the following packages installed
- typescript@^5.2.2
- vue@^3.3.4
- prettier@^3.0.3
Additionally vue-declassified requires Node 18+
pnpm add @heatsrc/vue-declassified
# or
npm install @heatsrc/vue-declassified
# or
yarn install @heatsrc/vue-declassified
Code
import { convertSfc } from "@heatsrc/vue-declassified";
import {readFile, writeFile} from 'node:fs/promises';
const input = "./myVueComponent.vue";
const output = "./myVueComponent.converted.vue";
(async () => {
const encoding = {encoding: 'utf8'};
const inputFile = await readFile(input, encoding);
const result = await convertSfc(input, output);
await writeFile(output, encoding);
}());
import { convertScript } from "@heatsrc/vue-declassified";
const input = `
import { Component } from 'vue-class-component';
@Component()
export default class MyComponent extends Vue {
myData: string = 'foo';
}`;
const result = convertScript(input);
console.log(result);
// import { ref } from 'vue';
// const myData = ref<string>('foo');
Supported Features
Legend | |
---|---|
✅ | Currently supported |
✔️ | Not currently being supported but being worked on |
💤 | Support is not prioritized |
💥 | No transform path to script setup (breaking change in Vue 2 -> 3) |
🚀 | All planned features are supported in section, let go! |
vue-class-component
Basic class transforms (5 ✅ / 2 ✔️)
feature | supported? | notes |
---|---|---|
methods | ✅ | Basic method support (no decorators) |
data properties | ✅ | Basic class properties (no decorators) |
getters/setters | ✅ | Computed refs |
mixins | ✔️ | |
extend | ✔️ | |
sort by dependency | ✅ | Will try to sort dependencies* |
$refs:! {...} |
✅ | converted to regular Ref s |
* VueDc does it best to sort dependencies to avoid used before defined issues. It requires processing essentially a directed acyclic graph and it's complicated so please raise issues if found.
Lifecycle Hooks (11/11 🚀)
lifecycle hooks | supported? | notes |
---|---|---|
beforeCreate | ✅ | body contents moved to root of script setup body |
created | ✅ | body contents moved to root of script setup body |
beforeMount | ✅ | onBeforeMount |
mounted | ✅ | onMounted |
beforeUpdate | ✅ | onBeforeUpdate |
updated | ✅ | onUpdated |
activated | ✅ | onActivated |
deactivated | ✅ | onDeactivated |
beforeDestroy | ✅ | onBeforeDestroy |
destroyed | ✅ | onDestroy |
errorCaptured | ✅ | onErrorCaptured |
this.
(11 ✅ / 3 ✔️ / 5 💥)
this. |
supported? | notes |
---|---|---|
PropertyAccess | ✅ | Primitives: Ref , Complex: Reactive , Uninitialized: Regular variables |
methods | ✅ | |
$attrs |
✔️ | Via const attrs = useAttrs() |
$data |
✅ | Treated same as data Class PropertyAssignments |
$emit |
✅ | Via const emit = defineEmits<...>() |
$nextTick |
✅ | Via import { nextTick } from 'vue'; |
$parent |
💥 | Refactor your code. Prop/Emits or Provide/Inject* |
$children |
💥 | - |
$props |
✅ | Via const props = defineProps<...>() |
$refs |
✅ | |
$route |
✅ | Via const route = useRoute(); |
$router |
✅ | Via const router = useRouter(); |
$slots |
✔️ | Via const slots = defineSlots<...>() |
$scopedSlots |
✔️ | Via const slots = defineSlots<...>() |
$store |
✅ | Via const store = useStore(); |
$watch |
✅ | Via import { watch } from 'vue'; |
$on |
💥 | |
$once |
💥 | |
$off |
💥 |
@Component
/ @Options
(v8.0.0-rc.1)
These are options provided in the decorator call, e.g., @Component({ components: { MyIcon } })
. All Options API fields are technically supported in Vue Class Components (e.g., data, computed, methods, etc) but many of them don't make sense and will not be actively developed but PRs may be accepted.
Options-Data (4/4 🚀)
Options-Data | supported? | notes |
---|---|---|
data | 💤 | While you can add these what you even using VCC for? |
props | ✅ | |
propsData | 💤 | This is primarily a testing feature |
computed | 💤 | While you can add these what you even using VCC for? |
watch | ✅ | |
exposes | ✅ | RC Feature since Vue 3 require declaring exposed fields |
emits | ✅ | RC Feature since Vue 3 require declaring events that are emitted |
Options-Assets (2 ✔️ / 1 💤)
Options-Assets | supported? | notes |
---|---|---|
directives | ✔️ | Will attempt to rename directives if they don't match |
filters | ✔️ | Will be converted to simple methods, you'll need to fix pipe style filters in your html templates |
components | 💤 | If you chance the name of your imports this may break |
Options-Composition (1 ✔️ / 3 💤)
Options-Composition | supported? | notes |
---|---|---|
parent | 💤 | Seem hacky to be specifying a parent in VCC SFC |
mixins | 💤 | While you can add these what are you even using VCC for? |
extends | 💤 | - |
provide/inject | ✔️ |
Options-Misc (2 ✔️ / 4 💤)
Options-Misc | supported? | notes |
---|---|---|
name | 💤 | Doesn't make much sense an script setup |
delimiters | 💤 | |
functional | 💤 | If all it uses is props script setup will automatically be functional |
model | ✔️ | |
inheritAttrs | ✔️ | |
comments | 💤 | VueDc will try to preserve comments by default |
Options-DOM (4/4 💤)
Options-DOM | supported? | notes |
---|---|---|
el | 💤 | DOM Options are more suited for Options API |
template | 💤 | - |
render | 💤 | - |
renderError | 💤 | - |
Options-LifeCycleHooks (11/11 💤)
Options-LifeCycle Hooks | supported? | notes |
---|---|---|
beforeCreate | 💤 | While you can add these what you even using VCC for? |
created | 💤 | - |
beforeMount | 💤 | - |
mounted | 💤 | - |
beforeUpdate | 💤 | - |
updated | 💤 | - |
activated | 💤 | - |
deactivated | 💤 | - |
beforeDestroy | 💤 | - |
destroyed | 💤 | - |
errorCaptured | 💤 | - |
vue-property-decorator
Decorators
(6 ✅ 1 ✔️ / 3 💤)
decorator | supported? | notes |
---|---|---|
@Prop |
✅ | |
@PropSync |
💤 | |
@Model |
✔️ | |
@Watch |
✅ | |
@Provide |
✅ | |
@Inject |
✅ | |
@ProvideReactive |
💤 | |
@InjectReactive |
💤 | |
@Emit |
✅ | |
@Ref |
✅ | Currently parsing templates isn't in the works so ref aliases will require updating if used. |
vuex-class
Decorators
(4 ✅, 1 ✔️)
decorator | supported? | notes |
---|---|---|
@Action |
✅ | |
@Getter |
✅ | |
@Mutation |
✅ | |
@State |
✅ | |
namespace | ✔️ |
Misc features
Other features (6/6 🚀)
- ✅ Limited type inference
- If a node is untyped, will do a best guess at type (mostly primitive types only).
- When encountering
$emit
s will try to infer parameter names/types. - Fails back to
unknown
keyword if it's not certain.
- ✅ Sorting by dependencies
- Will sort statements so definitions occur before uses.
- ✅ Merging macros/props/etc
- If you're code is doing insane stuff like defining props in both the
@Component
options and as decorators,@Props
/@Emit
/ etc, vuedc will merge the definitions.
- If you're code is doing insane stuff like defining props in both the
- ✅ Naming collision detection
- Will detect collisions from imports, variable declarations and instance properties that have been converted to top level (e.g.,
$ref.button
=>button.value
).
- Will detect collisions from imports, variable declarations and instance properties that have been converted to top level (e.g.,
- ✅ Automatic macro definitions
- Props ->
defineProps
(also will addwithDefaults
if vuedc detects defaults). - Emit ->
defineEmits
.
- Props ->
- ✅ Composable definitions
- When former "builtin" globals such as
$store
/$router
/etc are found vuedc will automatically import and assign to a variable - e.g.,
const store = useStore()
- When former "builtin" globals such as
Tips / Gotchas
Directives / Component names
Vue expects directive and components to match their name (PascalCase
/camelCase
-> kebab-case
).
Currently Vuedc doesn't detect if you've used a different name and aliased it in the component options
<script lang="ts">
import {Component, Vue} from 'vue-class-components';
import myComponentVue from './MyComponent.vue';
import myDirective from './MyDirective.ts';
@Component({
components: { MyComponent: myComponentVue },
directives: { vMyDirective: myDirective }
})
export default Foo extends Vue {}
</script>
// will be converted to:
<script setup lang="ts">
import myComponentVue from "./MyComponent.vue";
import myDirective from "./MyDirective.ts";
</script>
<template>
<!-- This is a problem ! -->
<my-component v-my-directive />
</template>
So make sure to rename your imports to match what the template is calling.
e.g.,
<script setup lang="ts">
import MyComponent from "./MyComponent.vue";
import vMyDirective from "./vMyDirective.ts";
</script>
<template>
<my-component v-my-directive />
</template>
Naming collisions
It's strongly recommended you resolve potential naming collisions prior to converting your code, vuedc doesn't have complete knowledge of the codes intention and doesn't update templates (yet?) so it can't reliably rename variables for you.
Common reasons for naming collisions:
$refs
with same name as class members
Properties on the $refs
object get converted to top level variable declarations and can collide with other class members sharing the same name.
e.g.,
@Component
export default class Foo extends Vue {
foo = "bar";
$refs!: {
foo: HTMLDivElement;
};
}
// will be converted to
const foo = ref<string>("bar");
const foo = ref<HTMLDivElement>();
// ^? Cannot redeclare block-scoped variable 'foo'.ts(2451)
Top level identifiers
It's not uncommon for you to import a variable from another module and make it available as a class member with the same name.
e.g.,
import foo from './foo';
const bar = foo;
const { a, b: { c } } = {a: true, b: { c: false } };
const [d, e, {f}] = [1, 2, {f: 3}];
@Component
export default Foo {
foo = 'string';
a = 1;
b = 'b';
d = [1, 2, 3];
@Inject e: () => 'e';
@Action f: () => Promise<void>;
get bar() {
}
c() {
}
}
// will be converted to
import foo from './foo';
const bar = foo;
const { a, b: { c } } = {a: true, b: { c: false } };
const [d, e, {f}] = [1, 2, {f: 3}];
const foo = ref('string');
// ^? Cannot redeclare block-scoped variable 'foo'.ts(2451)
const a = ref(1);
// ^? Cannot redeclare block-scoped variable 'a'.ts(2451)
const b = ref('b');
// ^? Cannot redeclare block-scoped variable 'b'.ts(2451)
const d = reactive([1, 2, 3]);
// ^? Cannot redeclare block-scoped variable 'd'.ts(2451)
const e = inject<() => 'e'>('e');
// ^? Cannot redeclare block-scoped variable 'e'.ts(2451)
const f = (): Promise<void> => store.dispatch('f');
// ^? Cannot redeclare block-scoped variable 'f'.ts(2451)
const bar = computed(() => 'val');
// ^? Cannot redeclare block-scoped variable 'bar'.ts(2451)
const c = () => 'foo';
// ^? Cannot redeclare block-scoped variable 'c'.ts(2451)