Formatter for converting links in a legacy markup to markdown
The magic numbers used to check the diff, e.g., diff[0][0] == 1 &amp;&amp; diff[1][0] == 0

One small improvement is that fast_diff exports enum values that can be used instead, eg:
differencesArr[0][0] === diff.INSERT
differencesArr[1][0] === diff.EQUAL


The looping in formatLinks, which is really only useful if the word has a suffix which is also a link

The whole looping mechanism looks quite suspicious to me:

Only the first $$$ is replaced by the .replace(/\${3}/, '') - if the word contains multiple $$$s, those beyond the first will be left in place

Inside formatLinks, you perform tests on and examine only the first element of the diff array, which is strange if there happen to be multiple matches

Then you have to clean the string afterwards - eg OneMatch$$$TwoMatch$$$ThreeMatch turns into [One Match](OneMatch) Two Match Three MatchOne Match [Two Match](TwoMatch)One Match Two Match [Three Match](ThreeMatch), which needs to be transformed again. Seems inelegant and error-prone.



If there is a better way to tackle the problem.

It looks like there are two fundamental categories a substring can fall into:

One which is escaped, and no []()s should be produced, like the whole span of E$$$scapeMatch
One which is not escaped, like the whole span of OneMatch$$$TwoMatch

To put each substring into one of these categories, I'd split by $$$ which split true, unescaped matches. For example:
OneMatch$$$TwoMatch -&gt; ['OneMatch', 'TwoMatch']
E$$$scapeMatch -&gt; ['E$$$scapeMatch']

Then you can iterate over each possible match, transforming the normal matches to their proper format, and leaving the escaped matches alone (save for removing their $$$s). Join the array of results, and there'll be 2 things left to do:

Add spacing at the edges of match brackets, if they're next to a prefix or suffix (which can be done with a simple regex)
Insert spaces between each word outside of brackets (which can be done with a simple regex)

const parseInput = input =&gt; input
    // Split into an array containing separate unescaped matches, escaped sequences, or prefixes/suffixes:
    .split(/\${3}(?=[A-Z]|s?$)/)
    // Add []() brackets around unescaped matches and remove all $s:
    .map(transformSubstr)
    .join('')
    // Add spacing around brackets:
    .replace(/(?&lt;=\w)\[/g, ' [')
    // Except right before an &quot;s&quot; suffix:
    .replace(/\)(?!s)(?=\w)/g, ') ')
    // Add spaces between each word outside of brackets:
    .replace(
        /(?!s$)\w+(?=\[|$)/g,
        words =&gt; words.replace(/\B[A-Z]/g, ' $&amp;')
    );

const transformSubstr = (substr) =&gt; {
    const normalWordsMatch = substr.match(/\b[A-Z][a-z]+(?:[A-Z][a-z]+)+\b/g);
    // If this section is an escaped match, a prefix, or a suffix, there won't be a match
    if (!normalWordsMatch) return substr.replace(/\$/g, '');
    const words = normalWordsMatch[0].match(/[A-Z][a-z]+/g);
    return `[${words.join(' ')}](${words.join('')})`;
};



const tests = [
  "OneMatch{a1fc471a-9483-4af3-8960-7104aa8d3b97}$TwoMatch{183d6425-9d46-4c32-8ce1-55b02e15b3c1}$scapeMatch"                       // Escape Match
, "SimpleMatch"                          // [Simple Match](SimpleMatch)
, "Prefixed{12abb1ad-bc6b-49b9-af33-314c963350ac}$Suffix"              // [Match Match With](MatchMatchWith) Suffix
, "MatchesWithPlural{d63f36d7-04f1-446b-80d3-6c09885cb6f3}$Gets$$$Escaped"                // This Gets Escaped
]

const parseInput = input =&gt; input
    // Split into an array containing separate unescaped matches, escaped sequences, or prefixes/suffixes:
    .split(/\${3}(?=[A-Z]|s?$)/)
    // Add []() brackets around unescaped matches:
    .map(transformSubstr)
    .join('')
    // Add spacing around brackets:
    .replace(/(?&lt;=\w)\[/g, ' [')
    // Except right before an "s" suffix:
    .replace(/\)(?!s)(?=\w)/g, ') ')
    // Add spaces between each word outside of brackets:
    .replace(
        /(?!s$)\w+(?=\[|$)/g,
        words =&gt; words.replace(/\B[A-Z]/g, ' $&amp;')
    );

const transformSubstr = (substr) =&gt; {
    const normalWordsMatch = substr.match(/\b[A-Z][a-z]+(?:[A-Z][a-z]+)+\b/g);
    // If this section is an escaped match, a prefix, or a suffix, there won't be a match
    if (!normalWordsMatch) return substr.replace(/\$/g, '');
    const words = normalWordsMatch[0].match(/[A-Z][a-z]+/g);
    return `[${words.join(' ')}](${words.join('')})`;
};
for (const input of tests) {
  console.log(parseInput(input));
}



I don't see any need for either of the external libraries anymore.

A couple nitpicks about your original code, if you were to keep using it:
Always use const when you can, which should be possible 95% of the time - only use let to warn future readers of the code that you may be reassigning the variable in the future.
Avoid sloppy comparison with == and != - better to use === and !==, whose rules are much easier to understand.
Always declare variables before using them - with for (match of matches), you're implicitly creating a global match variable. (Use for (const match of matches) instead)
parseWord name? There can be multiple words inside the variable passed to parseWord, eg SimpleMatch (though there's no space). Maybe call it something like parseInput.
Single-letter match? The current pattern being used in all the code here so far requires words to contain at least two characters. Is that really desirable? Consider, for example, what you'd want to happen given markup of ACuteCat$$$ADog. If that's permitted, you'd just need to change the [a-z]+ to [a-z]*.