npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@heatsrc/vue-declassified

v3.2.3

Published

Transform Vue Class Components to Vue 3 script setup

Downloads

41

Readme

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.

  • 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
  • Mixins will be renamed to match composable conventions (see Tips/Gotchas: Mixins)
  • When transforming TypeScript files containing mixins a new composable will be appended to the file but the existing mixin will be left

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 create composables out of VCC Mixins
vuedc -i myMixin.ts

or run directly with hot loading

pnpm dlx @heatsrc/vuedc -i myVueComponent.vue -o myVueComponent.converted.vue
# or
npx @heatsrc/vuedc -i myVueComponent.vue -o myVueComponent.converted.vue
# or
yarn dlx @heatsrcvuedc -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

Options

Vuedc provides a very limited set of options to keep maintainability of the program sustainable.

export type VuedcOptions = {
  /**
   * When true Vuedc will not stringify the vue file but instead return the
   * variable collisions
   */
  stopOnCollisions?: boolean;
  /**
   * When provided Vuedc will attempt to find a tsconfig.json project file along
   * the path. If found it will use the compiler options from this file rather
   * than simple defaults.
   *
   * Note: Unless you need external file references (e.g., mixins), it's
   * recommended not providing this. Using your project can be *significantly*
   * slower as TS will need to compile your entire project and uses the file
   * system rather than an in-memory file system when no project is provided.
   */
  basePath?: string;
};
import { convertSfc, convertMixin } from "@heatsrc/vue-declassified";
import {readFile, writeFile} from 'node:fs/promises';
import { dirname, extName } from 'node:path';

const input = "./myVueComponent.vue";
const output = "./myVueComponent.converted.vue";

(async () => {
  const encoding = {encoding: 'utf8'};
  const ext = extName(input);
  const inputFile = await readFile(input, encoding);

  const result: string | undefined;
  if (extName === '.vue') {
    result = await convertSfc(input);
    // or with options
    // result = await convertSfc(input, {stopOnCollisions: true, basePath: dirname(input)});
  } else {
    result = await convertMixin(input);
    // or with options
    // result = await convertMixin(input, {stopOnCollisions: true, basePath: dirname(input)});
  }


  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 | | | :----------------: | ----------------------------------------------------------------- | | :white_check_mark: | Currently supported | | :heavy_check_mark: | Not currently being supported but being worked on | | :zzz: | Support is not prioritized | | :boom: | No transform path to script setup (breaking change in Vue 2 -> 3) | | :rocket: | All planned features are supported in section, let go! |

vue-class-component

| feature | supported? | notes | | :----------------: | :----------------: | --------------------------------------------------------------------------------------- | | methods | :white_check_mark: | Basic method support (no decorators) | | data properties | :white_check_mark: | Basic class properties (no decorators) | | getters/setters | :white_check_mark: | Computed refs | | mixins | :white_check_mark: | :exclamation:Requires basePath option to be set (see Tips/Gotchas: Mixins) | | extend | :heavy_check_mark: | | | sort by dependency | :white_check_mark: | Will try to sort dependencies* | | $refs:! {...} | :white_check_mark: | converted to regular Refs |

* 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 | supported? | notes | | :-------------: | :----------------: | ------------------------------------------------ | | beforeCreate | :white_check_mark: | body contents moved to root of script setup body | | created | :white_check_mark: | body contents moved to root of script setup body | | beforeMount | :white_check_mark: | onBeforeMount | | mounted | :white_check_mark: | onMounted | | beforeUpdate | :white_check_mark: | onBeforeUpdate | | updated | :white_check_mark: | onUpdated | | activated | :white_check_mark: | onActivated | | deactivated | :white_check_mark: | onDeactivated | | beforeDestroy | :white_check_mark: | onBeforeDestroy | | destroyed | :white_check_mark: | onDestroy | | errorCaptured | :white_check_mark: | onErrorCaptured |

| this. | supported? | notes | | :------------: | :----------------: | ------------------------------------------------------------------------ | | PropertyAccess | :white_check_mark: | Primitives: Ref, Complex: Reactive, Uninitialized: Regular variables | | methods | :white_check_mark: | | | $attrs | :heavy_check_mark: | Via const attrs = useAttrs() | | $data | :white_check_mark: | Treated same as data Class PropertyAssignments | | $emit | :white_check_mark: | Via const emit = defineEmits<...>() | | $nextTick | :white_check_mark: | Via import { nextTick } from 'vue'; | | $parent | :boom: | Refactor your code. Prop/Emits or Provide/Inject* | | $children | :boom: | - | | $props | :white_check_mark: | Via const props = defineProps<...>() | | $refs | :white_check_mark: | Converted to standard Ref<T> | | $route | :white_check_mark: | Viaconst route = useRoute(); | | $router | :white_check_mark: | Viaconst router = useRouter(); | | $slots | :heavy_check_mark: | Viaconst slots = defineSlots<...>() | |$scopedSlots| :heavy_check_mark: | Viaconst slots = defineSlots<...>() | | $store | :white_check_mark: | Viaconst store = useStore(); | | $watch | :white_check_mark: | Viaimport { watch } from 'vue'; | | $on | :boom: | | | $once | :boom: | | | $off` | :boom: | |

* Strategies to handle tightly coupled children in slots

@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 | supported? | notes | | :----------: | :----------------: | ---------------------------------------------------------------- | | data | :zzz: | While you can add these what you even using VCC for? | | props | :white_check_mark: | | | propsData | :zzz: | This is primarily a testing feature | | computed | :zzz: | While you can add these what you even using VCC for? | | watch | :white_check_mark: | | | exposes | :white_check_mark: | RC Feature since Vue 3 require declaring exposed fields | | emits | :white_check_mark: | RC Feature since Vue 3 require declaring events that are emitted |

| Options-Assets | supported? | notes | | :------------: | :----------------: | ------------------------------------------------------------------------------------------------- | | directives | :heavy_check_mark: | Will attempt to rename directives if they don't match | | filters | :heavy_check_mark: | Will be converted to simple methods, you'll need to fix pipe style filters in your html templates | | components | :zzz: | If you chance the name of your imports this may break |

| Options-Composition | supported? | notes | | :-----------------: | :----------------: | -------------------------------------------------------- | | parent | :zzz: | Seem hacky to be specifying a parent in VCC SFC | | mixins | :zzz: | While you can add these what are you even using VCC for? | | extends | :zzz: | - | | provide/inject | :heavy_check_mark: | |

| Options-Misc | supported? | notes | | :----------: | :----------------: | --------------------------------------------------------------------- | | name | :zzz: | Doesn't make much sense an script setup | | delimiters | :zzz: | | | functional | :zzz: | If all it uses is props script setup will automatically be functional | | model | :heavy_check_mark: | | | inheritAttrs | :heavy_check_mark: | | | comments | :zzz: | VueDc will try to preserve comments by default |

| Options-DOM | supported? | notes | | :---------: | :--------: | ------------------------------------------- | | el | :zzz: | DOM Options are more suited for Options API | | template | :zzz: | - | | render | :zzz: | - | | renderError | :zzz: | - |

| Options-LifeCycle Hooks | supported? | notes | | :---------------------: | :--------: | ---------------------------------------------------- | | beforeCreate | :zzz: | While you can add these what you even using VCC for? | | created | :zzz: | - | | beforeMount | :zzz: | - | | mounted | :zzz: | - | | beforeUpdate | :zzz: | - | | updated | :zzz: | - | | activated | :zzz: | - | | deactivated | :zzz: | - | | beforeDestroy | :zzz: | - | | destroyed | :zzz: | - | | errorCaptured | :zzz: | - |

vue-property-decorator

| decorator | supported? | notes | | :----------------: | :----------------: | -------------------------------------------------------------------------------------------- | | @Prop | :white_check_mark: | | | @PropSync | :zzz: | | | @Model | :heavy_check_mark: | | | @Watch | :white_check_mark: | | | @Provide | :white_check_mark: | | | @Inject | :white_check_mark: | | | @ProvideReactive | :zzz: | | | @InjectReactive | :zzz: | | | @Emit | :white_check_mark: | | | @Ref | :white_check_mark: | Currently parsing templates isn't in the works so ref aliases will require updating if used. |

vuex-class

| decorator | supported? | notes | | :---------: | :----------------: | ----- | | @Action | :white_check_mark: | | | @Getter | :white_check_mark: | | | @Mutation | :white_check_mark: | | | @State | :white_check_mark: | | | namespace | :white_check_mark: | |

Misc features

  • :white_check_mark: Limited type inference
    • If a node is untyped, will do a best guess at type (mostly primitive types only).
    • When encountering $emits will try to infer parameter names/types.
    • Fails back to unknown keyword if it's not certain.
  • :white_check_mark: Sorting by dependencies
    • Will sort statements so definitions occur before uses.
  • :white_check_mark: 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.
  • :white_check_mark: 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).
  • :white_check_mark: Automatic macro definitions
    • Props -> defineProps (also will add withDefaults if vuedc detects defaults).
    • Emit -> defineEmits.
  • :white_check_mark: 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()
  • :white_check_mark: Transforming Mixin
    • Typescript files can be provided to convertMixin and any mixin found in that file will have a composable analogue created and appended to the file.

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)

Reactive Variables

When Vuedc encounters a data property assigned to an Array or Object it will assume you want to wrap it in reactive. This can be a problem if you're reassigning the variable in the code. Vuedc will assign the variable to a const so it should be immediately apparently that something is wrong and it's up to you how to refactor it.

You can convert the reactive to ref and add .value to the reassignments but you will lose deep reactivity of those variables (which may be the intention.)

Transforming Components extending Mixins

:exclamation: Note: Mixins support only works when you provide convertSfc with a basePath in the VuedcOptions (or with the -p | --project flag in the Vuedc cli tool).

When mixins are detected Vuedc will assume you've created a composable analog in the same file and perform the following

  • Transform
    • First char of mixin will be capitalized: (fooMixin -> FooMixin)
    • use will be prefixed: (FooMixin -> useFooMixin)
    • Mixin will be removed (if detected): (useFooMixin -> useFoo)
  • Exported variables are mapped 1:1 with public members of Mixin
    • e.g., const { isLoading, bar, fetchData } = useFoo();
  • All public members belonging to the mixin will be imported by the component
  • Property/Accessors will have .value added to the property access
    • e.g., const isReady = computed(() => !isLoading.value)

:information_source: Vuedc cannot (yet) detect which mixin members have been used in the template so will just import them all and you should refactor to remove unused variables.

:information_source: The old mixin isn't deleted from the import so it's up to you to clean it up.