- Moved custom node definitions from `composables/processFlowNodes.js` to individual `.vue` files for better production compatibility and maintainability. - Updated `ProcessFlowCanvas.vue` to import node types from the new file structure, ensuring proper rendering of custom nodes. - Added new methods for setting nodes and edges directly in the `ProcessFlowCanvas` component, improving flexibility in managing flow state. - Removed the deprecated `test.vue` file to streamline the codebase and eliminate unnecessary components. - Adjusted styles and structure in the process flow components to enhance user experience and maintain consistency across the application.
8.0 KiB
Vue Flow Custom Nodes Migration Guide
🎯 Problem Solved
Custom nodes defined inline (using template strings or object definitions) do not load in production with Vue Flow. The solution is to use separate .vue component files that are properly imported.
✅ Production-Safe Approach: File-Based Custom Nodes
1. Create Separate .vue Files for Each Node Type
Instead of defining nodes inline in composables/processFlowNodes.js
, create individual .vue
files:
components/process-flow/custom/
├── StartNode.vue
├── FormNode.vue
├── EndNode.vue
├── ConditionalNode.vue
├── ScriptNode.vue
├── ApiNode.vue
├── NotificationNode.vue
└── ... (other node types)
2. Standard Vue Component Structure
Each node component should follow this pattern:
<script setup>
import { Handle, Position } from '@vue-flow/core'
// Define props that Vue Flow passes to custom nodes
const props = defineProps([
'id', // Node ID
'type', // Node type
'label', // Node label
'selected', // Selection state
'data' // Custom data object
])
</script>
<template>
<div class="custom-node" :class="{ selected }">
<!-- Input handle (if needed) -->
<Handle
type="target"
:position="Position.Left"
class="node-handle"
/>
<!-- Node content -->
<div class="node-content">
<div class="node-icon">🚀</div>
<div class="node-label">{{ data?.label || label || 'Default Label' }}</div>
</div>
<!-- Output handle (if needed) -->
<Handle
type="source"
:position="Position.Right"
class="node-handle"
/>
</div>
</template>
<style scoped>
/* Component-specific styles */
.custom-node {
border-radius: 8px;
padding: 12px;
background: linear-gradient(135deg, #10b981, #059669);
color: white;
border: 2px solid #047857;
min-width: 120px;
}
.custom-node.selected {
border-color: #fbbf24;
box-shadow: 0 0 0 3px rgba(251, 191, 36, 0.3);
}
</style>
3. Import Components in Your Main File
In pages/process-builder/index.vue
:
// Import all custom node components
import StartNode from "~/components/process-flow/custom/StartNode.vue"
import FormNode from "~/components/process-flow/custom/FormNode.vue"
import EndNode from "~/components/process-flow/custom/EndNode.vue"
import ConditionalNode from "~/components/process-flow/custom/ConditionalNode.vue"
import ScriptNode from "~/components/process-flow/custom/ScriptNode.vue"
import ApiNode from "~/components/process-flow/custom/ApiNode.vue"
import NotificationNode from "~/components/process-flow/custom/NotificationNode.vue"
// ... import other nodes
4. Create nodeTypes Object
import { markRaw } from 'vue'
// Map node types to components (use markRaw to prevent reactivity issues)
const nodeTypes = {
'start': markRaw(StartNode),
'form': markRaw(FormNode),
'end': markRaw(EndNode),
'conditional': markRaw(ConditionalNode),
'script': markRaw(ScriptNode),
'api': markRaw(ApiNode),
'notification': markRaw(NotificationNode),
// ... other node types
}
5. Use nodeTypes in VueFlow Component
<template>
<VueFlow
v-model:nodes="nodes"
v-model:edges="edges"
:node-types="nodeTypes"
@error="handleVueFlowError"
>
<Background />
<Controls />
</VueFlow>
</template>
🚫 What NOT to Use (Breaks in Production)
❌ Template Slots Approach
<!-- This breaks in production -->
<VueFlow v-model:nodes="nodes" v-model:edges="edges">
<template #node-start="{ data, label }">
<div class="start-node">{{ label }}</div>
</template>
</VueFlow>
❌ Inline Object Definitions
// This breaks in production
const nodeTypes = {
'start': {
props: ['id', 'data'],
template: `<div>{{ data.label }}</div>`
}
}
📋 Migration Checklist
Current State Analysis
- Review
composables/processFlowNodes.js
for all node type definitions - Identify unique node types used in your process builder
- Note any complex computed properties or methods in existing nodes
Migration Steps
- Create individual
.vue
files for each node type incomponents/process-flow/custom/
- Convert existing node logic from
processFlowNodes.js
to Vue component format - Import all node components in main process builder file
- Create
nodeTypes
object mapping types to components withmarkRaw
- Update VueFlow component to use
:node-types
prop instead of template slots - Remove old
processFlowNodes.js
file - Test all node types render correctly
- Test in production environment
Key Node Types to Migrate
Based on your processFlowNodes.js
, you have these node types:
start
- Start nodesform
- Form nodesend
- End nodesconditional
- Conditional/gateway nodesscript
- Script execution nodesapi
- API call nodesnotification
- Notification nodessubprocess
- Subprocess nodes- Various shape nodes (rectangle, circle, diamond, etc.)
🔧 Common Patterns for Migration
Computed Properties
Convert computed properties from options API to composition API:
// OLD (in processFlowNodes.js)
computed: {
nodeLabel() {
return this.data?.label || 'Default'
}
}
// NEW (in .vue component)
const nodeLabel = computed(() => {
return props.data?.label || 'Default'
})
Event Handlers
// OLD
methods: {
onClick() {
this.$emit('node-click', this.id)
}
}
// NEW
const emit = defineEmits(['node-click'])
const onClick = () => {
emit('node-click', props.id)
}
Complex Node Logic
For nodes with complex logic (like ConditionalNode), maintain the same patterns but convert to composition API:
<script setup>
import { computed } from 'vue'
import { Handle, Position } from '@vue-flow/core'
const props = defineProps(['id', 'type', 'data', 'selected'])
const totalConditions = computed(() => {
return props.data?.conditions?.length || 0
})
const conditionSummary = computed(() => {
if (totalConditions.value === 0) return 'No conditions'
return `${totalConditions.value} condition${totalConditions.value > 1 ? 's' : ''}`
})
</script>
🚀 Benefits of File-Based Approach
- Production Compatibility - Works reliably in all environments
- Better Performance - Proper component compilation and tree-shaking
- Developer Experience - Full IDE support, syntax highlighting, linting
- Maintainability - Separate files are easier to manage and debug
- Reusability - Components can be imported and used elsewhere
- Scoped Styles - No CSS conflicts between node types
🐛 Debugging Tips
Error Handling
Add error handling to catch Vue Flow issues:
const handleVueFlowError = (error) => {
console.error('Vue Flow Error:', error)
if (isErrorOfType(error, ErrorCode.MISSING_VIEWPORT_DIMENSIONS)) {
console.error('Container needs explicit width/height')
}
}
Node ID Conflicts
Ensure all node IDs are unique across your application to prevent rendering conflicts.
📁 File Structure After Migration
components/process-flow/custom/
├── StartNode.vue
├── FormNode.vue
├── EndNode.vue
├── ConditionalNode.vue
├── ScriptNode.vue
├── ApiNode.vue
├── NotificationNode.vue
├── SubprocessNode.vue
├── RectangleShapeNode.vue
├── CircleShapeNode.vue
├── DiamondShapeNode.vue
├── TriangleShapeNode.vue
├── PentagonShapeNode.vue
├── HexagonShapeNode.vue
└── OctagonShapeNode.vue
pages/process-builder/
└── index.vue (updated to import and use file-based nodes)
# Remove after migration:
composables/processFlowNodes.js ❌
🎯 Ready to Migrate?
When you're ready to start the migration, provide:
- Your current
pages/process-builder/index.vue
file - Your current
composables/processFlowNodes.js
file - Any specific node types you want to prioritize
The assistant will help you convert each node type to the production-safe file-based approach! 🚀