BPMN.js Differ
bpmn-js-differ is a semantic diffing utility specifically designed for comparing two BPMN 2.0 files programmatically. It analyzes the structural and semantic differences between two BPMN definitions, producing a detailed report of added, removed, changed, and layout-modified elements. The current stable version is 3.2.0. The library maintains a steady release cadence, often aligning with updates to its core dependencies like `bpmn-moddle` and `bpmn-js`. Its key differentiator lies in providing a structured, semantic diff output, which is crucial for building visual diff tools or automated change detection workflows for BPMN diagrams, as opposed to simple text-based comparisons.
Common errors
-
ERR_REQUIRE_ESM
cause `bpmn-js-differ` v3.0.0+ is an ES module, but you are trying to `require()` it in a CommonJS context.fixChange `const { diff } = require('bpmn-js-differ');` to `import { diff } from 'bpmn-js-differ';` and ensure your project is configured for ESM. -
TypeError: Cannot read properties of undefined (reading 'length')
cause The `diff` function expects valid BPMN definition objects, typically produced by `bpmn-moddle`. This error often occurs when invalid inputs (e.g., `undefined`, raw XML strings, or incorrectly parsed objects) are provided.fixAlways parse your BPMN XML using `bpmn-moddle` (e.g., `bpmnModdle.fromXML(xml)`) and pass the resulting `rootElement` (definitions) to `diff`. -
SyntaxError: The requested module 'bpmn-js-differ' does not provide an export named 'default'
cause You are attempting a default import (`import BpmnDiffer from 'bpmn-js-differ';`) but the library only provides named exports (specifically `diff`).fixUse a named import: `import { diff } from 'bpmn-js-differ';`.
Warnings
- breaking Starting with version 3.0.0, `bpmn-js-differ` is distributed as an ES module (ESM) only. This means that `require()` statements for CommonJS environments will no longer work and must be replaced with `import` syntax.
- breaking Version 3.0.0 introduced a fundamental change by building the diffing logic on top of `bpmn-moddle`'s infrastructure. While the public `diff` function signature generally remained stable, this change mandates the use of `bpmn-moddle` (specifically version 9.0.0 or higher) to parse BPMN XML into the expected definition objects. Directly passing raw XML strings or objects from older parsing utilities will not work.
- gotcha Prior to version 3.0.1, `bpmn-js-differ` might not have reliably detected changes to an element's `$type` property. For example, changing a `bpmn:Task` to a `bpmn:ServiceTask` might have been missed or incorrectly reported.
Install
-
npm install bpmn-js-differ -
yarn add bpmn-js-differ -
pnpm add bpmn-js-differ
Imports
- diff
const { diff } = require('bpmn-js-differ');import { diff } from 'bpmn-js-differ'; - BpmnModdle
const { BpmnModdle } = require('bpmn-moddle');import { BpmnModdle } from 'bpmn-moddle'; - BpmnJsDifferTypes
import type { Change, Changes } from 'bpmn-js-differ';
Quickstart
import { diff } from 'bpmn-js-differ';
import { BpmnModdle } from 'bpmn-moddle';
// Example BPMN XML strings (replace with your actual diagrams)
const diagramAXML = `<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" id="Definitions_1">
<bpmn:process id="Process_1" isExecutable="false">
<bpmn:startEvent id="StartEvent_1" name="Start"></bpmn:startEvent>
<bpmn:task id="Task_1" name="Original Task"></bpmn:task>
<bpmn:sequenceFlow id="Flow_1" sourceRef="StartEvent_1" targetRef="Task_1"></bpmn:sequenceFlow>
</bpmn:process>
</bpmn:definitions>`;
const diagramBXML = `<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" id="Definitions_1">
<bpmn:process id="Process_1" isExecutable="false">
<bpmn:startEvent id="StartEvent_1" name="Start"></bpmn:startEvent>
<bpmn:task id="Task_1" name="Modified Task"></bpmn:task>
<bpmn:endEvent id="EndEvent_1" name="End"></bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1" sourceRef="StartEvent_1" targetRef="Task_1"></bpmn:sequenceFlow>
<bpmn:sequenceFlow id="Flow_2" sourceRef="Task_1" targetRef="EndEvent_1"></bpmn:sequenceFlow>
</bpmn:process>
</bpmn:definitions>`;
async function compareBpmnDiagrams(xmlA, xmlB) {
const bpmnModdle = new BpmnModdle();
const { rootElement: definitionsA } = await bpmnModdle.fromXML(xmlA);
const { rootElement: definitionsB } = await bpmnModdle.fromXML(xmlB);
const changes = diff(definitionsA, definitionsB);
console.log('Detected Changes:');
console.log(' Added:', Object.keys(changes._added));
console.log(' Removed:', Object.keys(changes._removed));
console.log(' Changed:', Object.keys(changes._changed));
console.log(' Layout Changed:', Object.keys(changes._layoutChanged));
return changes;
}
compareBpmnDiagrams(diagramAXML, diagramBXML)
.catch(console.error);