📖 3 minutes read
When maintaining legacy Laravel dashboards that use Vue 1.x but planning migration to Vue 2.x, you can build forward-compatible components using template literals instead of single-file components.
The Problem
Vue 1.x doesn’t support modern single-file component syntax with <template>, <script>, and <style> tags. Direct Vue 2.x components break in Vue 1.x runtimes.
The Solution
Export components as JavaScript objects with template literals:
// Compatible with both Vue 1.x and 2.x
const DataTableComponent = {
data() {
return {
items: [],
loading: false
}
},
template: `
<div class="data-table">
<div v-if="loading">Loading...</div>
<table v-else>
<tr v-for="item in items">
<td>{{ item.name }}</td>
</tr>
</table>
</div>
`,
mounted() {
this.fetchData()
},
methods: {
fetchData() {
// API call logic
}
}
}
// Export for global registration
if (typeof window !== 'undefined') {
window.DataTableComponent = DataTableComponent
}
Integration Pattern
- Compile in webpack/mix:
resources/assets/js/components.js - Export to window in old dashboard
- Register in legacy Vue 1.x app:
Vue.component('data-table', window.DataTableComponent) - Use in Blade:
<data-table :config="config"></data-table> - Same component works when you upgrade to Vue 2.x later
Why This Works
- Template literals are standard ES6 JavaScript: Both Vue versions understand them
- Plain component object definitions: Vue 1.x and 2.x both support this format
- Window exports bypass module incompatibilities: No need for different build setups
Benefits
You maintain one codebase instead of duplicating components across Vue versions during multi-month migrations. When you finally upgrade to Vue 2.x, these components continue working without any changes.
Real-World Example
const ReportBuilderComponent = {
props: ['initialFilters'],
data() {
return {
filters: this.initialFilters || {},
results: [],
loading: false
}
},
template: `
<div class="report-builder">
<div class="filters">
<input v-model="filters.startDate" type="date">
<input v-model="filters.endDate" type="date">
<button @click="runReport">Generate</button>
</div>
<div v-if="loading" class="loading">Loading...</div>
<table v-else class="results">
<tr v-for="row in results">
<td>{{ row.metric }}</td>
<td>{{ row.value }}</td>
</tr>
</table>
</div>
`,
methods: {
async runReport() {
this.loading = true
const response = await fetch('/api/reports', {
method: 'POST',
body: JSON.stringify(this.filters)
})
this.results = await response.json()
this.loading = false
}
}
}
if (typeof window !== 'undefined') {
window.ReportBuilderComponent = ReportBuilderComponent
}
This pattern has saved countless hours during gradual Vue upgrades in legacy Laravel applications.
Leave a Reply