How to configure VueJS with a single runtime across multiple scripts in a Vite application?
I’m building an application with Vite and VueJS that includes both main application scripts and various plugin scripts. These plugin scripts are loaded independently of the main application and can be built at different times.
My current Vite configuration is:
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [
vue(),
laravel({
input: ['src/frontend.js'],
refresh: true,
}),
],
resolve: {
alias: {
'~root': __dirname + "/node_modules/",
vue: 'vue/dist/vue.esm-bundler.js',
},
},
});
However, while my plugin components appear correctly, features like v-model don’t work on these components. I suspect this might be due to conflicts between different VueJS runtimes, but I’m not sure how to implement a single runtime across all scripts to rule this out.
There are no errors or warnings related to this behavior, making it difficult to diagnose the issue.
To configure VueJS with a single runtime across multiple scripts in a Vite application, you need to ensure all scripts share the same Vue instance and runtime configuration. The key is to properly alias the Vue runtime reference and manage your build configuration to avoid runtime conflicts.
Contents
- Understanding the Runtime Conflict Problem
- Configuring a Single Vue Runtime
- Managing Multiple Script Builds
- Resolving v-Model Conflicts
- Plugin Architecture Best Practices
- Testing and Debugging Runtime Issues
- Advanced Configuration Options
Understanding the Runtime Conflict Problem
When working with multiple scripts in a Vite + VueJS application, each script can potentially load its own version of Vue. This creates multiple isolated Vue runtimes that don’t communicate with each other. As Vue.js documentation explains, each runtime maintains its own reactivity system, which means components from different runtimes cannot interact properly.
The symptoms you’re experiencing with v-model not working on plugin components are classic indicators of runtime conflicts. Each plugin script might be initializing its own Vue instance, preventing proper data binding and reactivity between components.
Common causes of runtime conflicts:
- Different versions of Vue loaded across scripts
- Separate Vue instances created in different entry points
- Improper module resolution leading to multiple Vue copies
- Lack of shared dependencies between build targets
Configuring a Single Vue Runtime
Your current configuration is on the right track with the Vue alias, but it needs refinement. Here’s how to ensure a single runtime:
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [
vue(),
laravel({
input: ['src/frontend.js'],
refresh: true,
}),
],
resolve: {
alias: {
'~root': __dirname + "/node_modules/",
// Force all imports of Vue to use the same bundler version
vue: 'vue/dist/vue.esm-bundler.js',
},
},
// Add shared dependencies configuration
optimizeDeps: {
include: ['vue']
},
build: {
// Ensure consistent module resolution
commonjsOptions: {
include: [/node_modules/]
}
}
});
Key improvements:
- OptimizeDeps: The
optimizeDeps.include: ['vue']ensures Vue is pre-bundled and shared across all scripts - Strict aliasing: The
vue: 'vue/dist/vue.esm-bundler.js'forces all Vue imports to use the same version - Build configuration: Proper commonjs handling prevents module resolution issues
Managing Multiple Script Builds
For your plugin architecture, you’ll need separate build configurations that share the same Vue runtime. Here’s a comprehensive approach:
Create separate config files:
// vite.config.main.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
vue: 'vue/dist/vue.esm-bundler.js',
},
},
build: {
outDir: 'dist/main',
rollupOptions: {
input: 'src/main.js',
output: {
format: 'es',
entryFileNames: 'main.js'
}
}
}
});
// vite.config.plugins.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
vue: 'vue/dist/vue.esm-bundler.js',
},
},
build: {
outDir: 'dist/plugins',
rollupOptions: {
input: {
'plugin1': 'src/plugins/plugin1.js',
'plugin2': 'src/plugins/plugin2.js',
},
output: {
format: 'es',
entryFileNames: '[name].js'
}
}
}
});
Update package.json scripts:
{
"scripts": {
"dev": "vite",
"build:main": "vite build --config vite.config.main.js",
"build:plugins": "vite build --config vite.config.plugins.js",
"build": "npm run build:main && npm run build:plugins"
}
}
This approach, as demonstrated in StackOverflow examples, ensures each script has its own build process while sharing the same Vue runtime.
Resolving v-Model Conflicts
The v-model issues you’re experiencing likely stem from data binding conflicts between different Vue instances. Here are solutions:
1. Use a global event bus for inter-plugin communication:
// shared/event-bus.js
import mitt from 'mitt';
export const eventBus = mitt();
// In plugin1.js
import { eventBus } from '../shared/eventBus.js';
export default {
data() {
return {
inputValue: ''
}
},
template: `<input v-model="inputValue" @input="onInput">`,
methods: {
onInput(e) {
// Emit to other plugins
eventBus.emit('input-change', e.target.value);
}
}
}
// In plugin2.js
import { eventBus } from '../shared/eventBus.js';
export default {
data() {
return {
receivedValue: ''
}
},
created() {
eventBus.on('input-change', (value) => {
this.receivedValue = value;
});
}
}
2. Implement proper v-model with props and events:
// Base plugin component
export default {
props: ['modelValue'],
emits: ['update:modelValue'],
template: `<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
>`
}
// Usage in main app
<template>
<main-app>
<plugin1 v-model="sharedValue" />
<plugin2 v-model="sharedValue" />
</main-app>
</template>
This approach follows Vue’s component v-model guidelines and prevents conflicts by using a single source of truth.
Plugin Architecture Best Practices
1. Shared dependencies:
// vite.config.shared.js
export default {
resolve: {
alias: {
vue: 'vue/dist/vue.esm-bundler.js',
// Other shared dependencies
}
},
optimizeDeps: {
include: ['vue', 'vue-router', 'pinia']
}
}
2. Plugin communication layer:
// shared/plugin-manager.js
export class PluginManager {
constructor() {
this.plugins = new Map();
this.sharedState = reactive({});
}
registerPlugin(name, plugin) {
this.plugins.set(name, plugin);
}
updateSharedState(key, value) {
this.sharedState[key] = value;
}
}
export const pluginManager = new PluginManager();
3. Build plugin entry points:
// src/plugins/plugin1.js
import { createApp } from 'vue';
import Plugin1Component from './Plugin1.vue';
import { pluginManager } from '../shared/plugin-manager.js';
export function initPlugin1() {
const app = createApp(Plugin1Component, {
sharedState: pluginManager.sharedState
});
app.mount('#plugin1-container');
pluginManager.registerPlugin('plugin1', app);
}
This architecture ensures all plugins share the same Vue runtime and can communicate effectively.
Testing and Debugging Runtime Issues
1. Runtime version checking:
// debug-runtime.js
console.log('Vue version:', window.Vue?.version);
console.log('Vue instances:', Object.keys(window.Vue?._app || {}));
2. Component inspection:
// In browser console
// Check if components are properly registered
window.Vue?.config?.globalProperties.$components?.forEach(comp => {
console.log('Component:', comp.name);
});
// Check reactivity
console.log('Reactive systems:', Object.keys(window.Vue?._app?.config?.globalProperties || {}));
3. Build verification:
# Check bundle contents
npm run build:main
npm run build:plugins
# Verify Vue instances in built files
grep -n "Vue\.version" dist/main/main.js
grep -n "Vue\.version" dist/plugins/plugin1.js
4. Development debugging:
// Add to your Vite config for development
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
vue: 'vue/dist/vue.esm-bundler.js',
},
},
define: {
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: true,
}
});
Advanced Configuration Options
1. Webpack-style externals for shared libraries:
export default defineConfig({
build: {
rollupOptions: {
external: ['vue'],
output: {
globals: {
vue: 'Vue'
}
}
}
}
});
2. Multi-page application approach:
// vite.config.js
export default defineConfig({
plugins: [vue()],
build: {
rollupOptions: {
input: {
main: './src/main.js',
plugin1: './src/plugins/plugin1.js',
plugin2: './src/plugins/plugin2.js',
}
}
}
});
3. Runtime compilation optimization:
export default defineConfig({
plugins: [
vue({
template: {
compilerOptions: {
// Optimize for plugin components
isCustomElement: tag => tag.startsWith('plugin-')
}
}
})
],
resolve: {
alias: {
vue: 'vue/dist/vue.esm-bundler.js',
},
}
});
Sources
- Vite Getting Started Guide - Official Vite documentation covering build configuration and multi-page apps
- Stack Overflow: Multiple app output directories for Vite Vue 3 setup - Examples of managing multiple build configurations
- Vue.js Component v-model Guide - Official documentation on v-model implementation and conflicts
- Vue.js SFC Script Setup Documentation - Information about multiple script blocks in Vue components
- Vite Discussions: Runtime template compiler - Configuration examples for runtime compiler setup
- Vite Discussions: Build multiple JS files - Advanced build configuration techniques
- Reddit: Vite compiling multiple JS files - Community solutions for plugin architecture
Conclusion
Configuring VueJS with a single runtime across multiple scripts in a Vite application requires careful attention to module resolution and build configuration. The key takeaways are:
- Use consistent Vue aliases across all build configurations to ensure all scripts reference the same Vue runtime
- Implement shared dependency management through
optimizeDepsand proper module resolution - Create separate build configs for main and plugin scripts while maintaining runtime consistency
- Establish clear communication patterns between plugins using event buses or shared state management
- Test thoroughly for runtime conflicts by checking Vue instances and reactivity systems
By following these practices, you’ll be able to build a robust plugin architecture where all components share the same Vue runtime and features like v-model work seamlessly across your entire application. Start with the basic configuration and gradually add complexity as your plugin system grows.