corrad-bp/docs/vue-flow-custom-nodes-migration.md
Md Afiq Iskandar f8a67c4467 Refactor Process Flow Nodes and Enhance ProcessFlowCanvas Component
- 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.
2025-07-21 11:21:58 +08:00

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

  1. Create individual .vue files for each node type in components/process-flow/custom/
  2. Convert existing node logic from processFlowNodes.js to Vue component format
  3. Import all node components in main process builder file
  4. Create nodeTypes object mapping types to components with markRaw
  5. Update VueFlow component to use :node-types prop instead of template slots
  6. Remove old processFlowNodes.js file
  7. Test all node types render correctly
  8. Test in production environment

Key Node Types to Migrate

Based on your processFlowNodes.js, you have these node types:

  • start - Start nodes
  • form - Form nodes
  • end - End nodes
  • conditional - Conditional/gateway nodes
  • script - Script execution nodes
  • api - API call nodes
  • notification - Notification nodes
  • subprocess - 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

  1. Production Compatibility - Works reliably in all environments
  2. Better Performance - Proper component compilation and tree-shaking
  3. Developer Experience - Full IDE support, syntax highlighting, linting
  4. Maintainability - Separate files are easier to manage and debug
  5. Reusability - Components can be imported and used elsewhere
  6. 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:

  1. Your current pages/process-builder/index.vue file
  2. Your current composables/processFlowNodes.js file
  3. Any specific node types you want to prioritize

The assistant will help you convert each node type to the production-safe file-based approach! 🚀