NeuroAgent

How to Configure VueJS Single Runtime in Vite

Learn how to configure VueJS with a single runtime across multiple scripts in Vite applications. Fix v-model conflicts and ensure proper plugin functionality with our comprehensive guide.

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:

javascript
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.

NeuroAgent

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

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:

javascript
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:

  1. OptimizeDeps: The optimizeDeps.include: ['vue'] ensures Vue is pre-bundled and shared across all scripts
  2. Strict aliasing: The vue: 'vue/dist/vue.esm-bundler.js' forces all Vue imports to use the same version
  3. 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:

javascript
// 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:

json
{
  "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:

javascript
// 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:

javascript
// 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:

javascript
// 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:

javascript
// 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:

javascript
// 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:

javascript
// debug-runtime.js
console.log('Vue version:', window.Vue?.version);
console.log('Vue instances:', Object.keys(window.Vue?._app || {}));

2. Component inspection:

javascript
// 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:

bash
# 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:

javascript
// 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:

javascript
export default defineConfig({
    build: {
        rollupOptions: {
            external: ['vue'],
            output: {
                globals: {
                    vue: 'Vue'
                }
            }
        }
    }
});

2. Multi-page application approach:

javascript
// 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:

javascript
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

  1. Vite Getting Started Guide - Official Vite documentation covering build configuration and multi-page apps
  2. Stack Overflow: Multiple app output directories for Vite Vue 3 setup - Examples of managing multiple build configurations
  3. Vue.js Component v-model Guide - Official documentation on v-model implementation and conflicts
  4. Vue.js SFC Script Setup Documentation - Information about multiple script blocks in Vue components
  5. Vite Discussions: Runtime template compiler - Configuration examples for runtime compiler setup
  6. Vite Discussions: Build multiple JS files - Advanced build configuration techniques
  7. 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:

  1. Use consistent Vue aliases across all build configurations to ensure all scripts reference the same Vue runtime
  2. Implement shared dependency management through optimizeDeps and proper module resolution
  3. Create separate build configs for main and plugin scripts while maintaining runtime consistency
  4. Establish clear communication patterns between plugins using event buses or shared state management
  5. 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.