Skip to content

Migration from Vue 3 to Lyt.js

Lyt.js provides a highly compatible Vue 3 API through the @lytjs/compat compatibility layer, making migration straightforward and low-cost. This guide helps you quickly migrate your Vue 3 project to Lyt.js.

Overview

Why Migrate from Vue 3 to Lyt.js?

  • Lighter footprint: Lyt.js core is smaller with zero external dependencies
  • Dual reactivity modes: Supports both Proxy and Signal reactivity modes
  • More flexible: Provides more customization and extension capabilities
  • High compatibility: With @lytjs/compat, most Vue 3 code works seamlessly

Migration Strategy

  1. Incremental migration: Install @lytjs/compat first, then gradually replace imports
  2. Automated tooling: Use the vue-to-lyt CLI tool for automatic code conversion
  3. Manual adjustments: Handle incompatible parts

API Compatibility Reference

Reactivity API

Vue 3 APILyt.js (@lytjs/compat)CompatibilityNotes
ref()ref()Fully compatibleDirect use
reactive()reactive()Fully compatibleDirect use
computed()computed()Fully compatibleDirect use
watch()watch()Fully compatibleDirect use
watchEffect()watchEffect()Fully compatibleDirect use
watchPostEffect()watchPostEffect()Fully compatibleDirect use
watchSyncEffect()watchSyncEffect()Fully compatibleDirect use
shallowRef()shallowRef()Fully compatibleDirect use
shallowReactive()shallowReactive()Fully compatibleDirect use
triggerRef()triggerRef()Fully compatibleDirect use
readonly()readonly()Fully compatibleDirect use
isRef()isRef()Fully compatibleDirect use
isReactive()isReactive()Fully compatibleDirect use
isReadonly()isReadonly()Fully compatibleDirect use
isProxy()isProxy()Fully compatibleDirect use
toRaw()toRaw()Fully compatibleDirect use
markRaw()markRaw()Fully compatibleDirect use
toRef()toRef()Fully compatibleDirect use
toRefs()toRefs()Fully compatibleDirect use
unref()unref()Fully compatibleDirect use
proxyRefs()proxyRefs()PlaceholderUse reactive() or toRefs() instead
effect()effect()Fully compatibleDirect use
nextTick()nextTick()Fully compatibleDirect use

Lifecycle Hooks

Vue 3 APILyt.js (@lytjs/compat)CompatibilityNotes
onMounted()onMounted()Fully compatibleDirect use
onUpdated()onUpdated()Fully compatibleDirect use
onUnmounted()onUnmounted()Fully compatibleDirect use
onBeforeMount()onBeforeMount()Fully compatibleDirect use
onBeforeUpdate()onBeforeUpdate()Fully compatibleDirect use
onBeforeUnmount()onBeforeUnmount()Fully compatibleDirect use
onErrorCaptured()onErrorCaptured()PlaceholderPrints warning only
onRenderTracked()onRenderTracked()PlaceholderPrints warning only
onRenderTriggered()onRenderTriggered()PlaceholderPrints warning only
onActivated()onActivated()PlaceholderPrints warning only
onDeactivated()onDeactivated()PlaceholderPrints warning only
onServerPrefetch()onServerPrefetch()PlaceholderPrints warning only

Dependency Injection

Vue 3 APILyt.js (@lytjs/compat)CompatibilityNotes
provide()provide()Fully compatibleDirect use
inject()inject()Fully compatibleDirect use

Component API

Vue 3 APILyt.js (@lytjs/compat)CompatibilityNotes
createApp()createApp()Fully compatibleDirect use
defineComponent()defineComponent()Fully compatibleDirect use
defineAsyncComponent()defineAsyncComponent()Fully compatibleDirect use
h()h()Fully compatibleDirect use
FragmentFragmentFully compatibleDirect use
getCurrentInstance()getCurrentInstance()Fully compatibleDirect use
defineProps()defineProps()PlaceholderCompiler macro, use props option
defineEmits()defineEmits()PlaceholderCompiler macro, use emits option
withDefaults()withDefaults()PlaceholderCompiler macro, use props default
defineExpose()defineExpose()PlaceholderSetup return values auto-exposed
useSlots()useSlots()PlaceholderAccess via setup context
useAttrs()useAttrs()PlaceholderAccess via setup context
useTemplateRef()useTemplateRef()PlaceholderUse ref() instead

Built-in Components

Vue 3 ComponentLyt.js (@lytjs/compat)CompatibilityNotes
<KeepAlive><KeepAlive>Fully compatibleImport from @lytjs/compat
<Teleport><Teleport>PlaceholderBasic export, limited functionality
<Transition><Transition>Fully compatibleImport from @lytjs/compat
<TransitionGroup><TransitionGroup>Fully compatibleImport from @lytjs/compat
<Suspense><Suspense>Fully compatibleImport from @lytjs/compat

Template Directives

Vue 3 DirectiveLyt.js DirectiveCompatibilityNotes
v-ififSyntax changeRemove v- prefix
v-else-ifelse-ifSyntax changeRemove v- prefix
v-elseelseSyntax changeRemove v- prefix
v-forv-eachSyntax changev-for becomes v-each
v-modelmodelSyntax changeRemove v- prefix
v-model.trimmodel.trimSyntax changeModifier syntax identical
v-model.numbermodel.numberSyntax changeModifier syntax identical
v-model.lazymodel.lazySyntax changeModifier syntax identical
v-showshowSyntax changeRemove v- prefix
v-htmlhtmlSyntax changeRemove v- prefix
v-texttextSyntax changeRemove v- prefix
v-on:on:Syntax changev-on: becomes on:
@click@clickFully compatibleShorthand syntax identical
v-bind::Fully compatibleShorthand recommended
v-slot:slot:Syntax changev-slot: becomes slot:
#name#nameFully compatibleShorthand syntax identical
v-onceonceSyntax changeRemove v- prefix
v-prepreSyntax changeRemove v- prefix
v-cloakcloakSyntax changeRemove v- prefix
v-memo-Not supportedUse computed instead

Ecosystem

Vue 3 EcosystemLyt.js AlternativeCompatibilityNotes
Vue Router@lytjs/routerSimilar APIDifferent import path
Pinia@lytjs/storeSimilar APIAPI adjustments needed
Vuex@lytjs/storeDifferent APIRewrite state management

Migration Steps

Step 1: Install @lytjs/compat

bash
npm install @lytjs/compat

@lytjs/compat automatically installs these dependencies:

  • @lytjs/core
  • @lytjs/reactivity
  • @lytjs/component

Step 2: Replace Imports

javascript
// Vue 3
import { createApp, ref, reactive, computed, watch, onMounted } from 'vue'

// Lyt.js (using compat layer)
import { createApp, ref, reactive, computed, watch, onMounted } from '@lytjs/compat'

You can also use native Lyt.js packages directly:

javascript
import { createApp, h, Fragment } from '@lytjs/core'
import { ref, reactive, computed, watch, nextTick } from '@lytjs/reactivity'
import { defineComponent, onMounted, onUnmounted, provide, inject } from '@lytjs/component'

Step 3: Update Template Syntax

Conditional Rendering

html
<!-- Vue 3 -->
<div v-if="show">Content</div>
<div v-else-if="loading">Loading...</div>
<div v-else>Hidden</div>

<!-- Lyt.js -->
<div if="show">Content</div>
<div else-if="loading">Loading...</div>
<div else>Hidden</div>

List Rendering

html
<!-- Vue 3 -->
<li v-for="item in items" :key="item.id">{{ item.name }}</li>

<!-- Lyt.js -->
<li v-each="item in items" key="item.id">{{ item.name }}</li>

Two-Way Binding

html
<!-- Vue 3 -->
<input v-model="text" />
<input v-model.trim="text" />
<input v-model.number="count" />

<!-- Lyt.js -->
<input model="text" />
<input model.trim="text" />
<input model.number="count" />

Event Handling

html
<!-- Vue 3 -->
<button v-on:click="handleClick">Click</button>
<button @click="handleClick">Click</button>
<button @click.prevent="handleSubmit">Submit</button>

<!-- Lyt.js -->
<button on:click="handleClick">Click</button>
<button @click="handleClick">Click</button>
<button @click.prevent="handleSubmit">Submit</button>

Attribute Binding

html
<!-- Vue 3 -->
<img v-bind:src="imageUrl" />
<img :src="imageUrl" />

<!-- Lyt.js -->
<img :src="imageUrl" />
<img :src="imageUrl" />

Slots

html
<!-- Vue 3 -->
<template v-slot:header>Header</template>
<template #header>Header</template>

<!-- Lyt.js -->
<template slot="header">Header</template>
<template #header>Header</template>

Other Directives

html
<!-- Vue 3 -->
<div v-show="isVisible">Content</div>
<div v-html="rawHtml"></div>
<div v-text="message"></div>
<div v-once>Static</div>

<!-- Lyt.js -->
<div show="isVisible">Content</div>
<div html="rawHtml"></div>
<div text="message"></div>
<div once>Static</div>

Step 4: Replace Ecosystem Packages

Router

javascript
// Vue Router
import { createRouter, createWebHistory } from 'vue-router'

// Lyt.js Router
import { createRouter } from '@lytjs/router'

State Management

javascript
// Pinia
import { defineStore } from 'pinia'
const useCounter = defineStore('counter', {
  state: () => ({ count: 0 }),
  getters: { double: (s) => s.count * 2 },
  actions: { increment() { this.count++ } }
})

// Lyt.js Store
import { createStore } from '@lytjs/store'
const useCounter = createStore('counter', {
  state: () => ({ count: 0 }),
  getters: { double: (s) => s.count * 2 },
  actions: { increment(s) { s.count++ } }
})

Step 5: Run Migration Tool

Use the vue-to-lyt CLI tool for automatic code conversion:

bash
# Convert a single file
npx @lytjs/compat vue-to-lyt ./src/MyComponent.vue

# Convert an entire directory
npx @lytjs/compat vue-to-lyt ./src --recursive

# Preview conversion (dry run, no file writes)
npx @lytjs/compat vue-to-lyt ./src --recursive --dry-run

# Convert to a specific output directory
npx @lytjs/compat vue-to-lyt ./src --recursive --output ./lyt-src

You can also use the migration API programmatically:

typescript
import { migrateVueFile, formatMigrationReport } from '@lytjs/compat'

const source = `
<template>
  <div v-if="show">{{ message }}</div>
</template>
<script setup>
import { ref } from 'vue'
const message = ref('Hello')
</script>
`

const report = migrateVueFile(source)
console.log('Compatibility score:', report.compatibilityScore)
console.log('Converted code:', report.code)
console.log('Manual fixes needed:', report.manualFixes)
console.log(formatMigrationReport(report))

Step 6: Handle Incompatible Parts Manually

The migration tool automatically detects and flags parts that need manual adjustment. Common cases requiring manual work:

  • defineProps / defineEmits compiler macros
  • $refs / $emit / $el instance properties
  • CSS Modules
  • v-memo directive
  • Pinia/Vuex state management API differences

Common Migration Issues

1. v-model Differences

Vue 3's v-model becomes model in Lyt.js. Modifier syntax remains the same:

html
<!-- Vue 3 -->
<input v-model.trim="text" />
<input v-model.number="count" />
<input v-model.lazy="value" />

<!-- Lyt.js -->
<input model.trim="text" />
<input model.number="count" />
<input model.lazy="value" />

2. Lifecycle Hook Differences

Most lifecycle hooks are fully compatible. The following hooks have placeholder implementations:

  • onErrorCaptured -- Prints warning only, does not actually capture errors
  • onRenderTracked / onRenderTriggered -- Prints warning only
  • onActivated / onDeactivated -- Prints warning only
  • onServerPrefetch -- Prints warning only

3. Directive Differences

FeatureVue 3Lyt.js
Conditional renderingv-if / v-else-if / v-elseif / else-if / else
List renderingv-forv-each
Two-way bindingv-modelmodel
Event bindingv-on:click or @clickon:click or @click
Attribute bindingv-bind:src or :src:src
Slotsv-slot:name or #nameslot:name or #name

4. Component Library Differences

Vue 3 component libraries (such as Element Plus, Ant Design Vue) cannot be used directly in Lyt.js. Your options:

  1. Look for Lyt.js alternative component libraries
  2. Use native HTML elements with custom styles
  3. Wrap commonly used UI components yourself

5. $refs / $emit / $el Alternatives

javascript
// Vue 3
export default {
  mounted() {
    this.$refs.input.focus()
    this.$emit('change', this.value)
    console.log(this.$el)
  }
}

// Lyt.js (Composition API)
import { ref, onMounted } from '@lytjs/compat'

// setup() {
//   const inputRef = ref(null)
//   const emit = (event, ...args) => { /* use setup context emit */ }
//
//   onMounted(() => {
//     inputRef.value?.focus()
//     emit('change', value.value)
//   })
//
//   return { inputRef }
// }

6. defineProps / defineEmits Alternatives

javascript
// Vue 3 (script setup)
const props = defineProps({ title: String })
const emit = defineEmits(['update'])

// Lyt.js (defineComponent)
import { defineComponent } from '@lytjs/compat'

export default defineComponent({
  props: {
    title: String,
  },
  emits: ['update'],
  setup(props, { emit }) {
    // Use props.title and emit('update', value)
  },
})

Best Practices

1. Incremental Migration

Do not migrate the entire project at once. Follow this order:

  1. Migrate utility functions and pure logic modules first
  2. Then migrate simple presentational components
  3. Finally migrate complex interactive components

2. Use the @lytjs/compat Layer

During the initial migration phase, @lytjs/compat minimizes code changes. Once migration is complete, you can gradually switch to native Lyt.js packages for better type support and performance.

3. Leverage Migration Tools

Use the vue-to-lyt CLI tool and migrateVueFile API for automated conversion. The migration tool generates a compatibility score and detailed issue report to help you assess migration difficulty.

4. Write Migration Tests

After migration, ensure all functionality works correctly:

  • Write unit tests for each migrated component
  • Run end-to-end tests to verify user interactions
  • Check the console for compatibility layer warnings

5. Watch for Placeholder APIs

APIs marked as "placeholder" in the compatibility layer do not perform any actual operations; they only print warnings. If your code depends on these APIs, you need to find alternatives.

6. Take Advantage of Signal Mode

Lyt.js's unique Signal reactivity mode can provide better performance. After migration, consider switching some components from Proxy mode to Signal mode:

javascript
import { defineComponent } from '@lytjs/component'

export default defineComponent({
  reactivityMode: 'signal', // Use Signal mode
  setup() {
    // State management in Signal mode
  },
})

Getting Help

If you encounter issues during migration, please submit feedback through Gitee Issues.

Released under the MIT License.