JSFiddle - React, Tailwind, and code Playground

HTML

<body>
    <div id="panel">
        <label for="url">Dict URL:</label>
        <input id="url" type="text" name="dict_url" value="http://cartou.ch/sharedstuff/testdict.txt" />
        <label for="word_len">Word Length</label>
        <input id="word_len" type="number" value="11" />
        <label for="mask_count">Mask Count</label>
        <input id="mask_count" type="number" value="3" />
        <input type="checkbox" id="output_result" checked="checked" />
        <label for="output_result"> Output Result</label>
        <input type="button" id="button" value="Run" />
    </div>
    
<pre id="output" style="background-color: #222; color: #eee; width: 90ex; min-height: 20em; overflow: auto">
</pre> 
</body>

CSS

input[type=number] {
    max-width: 5ex;
}

CoffeeScript

MASK_CHAR = '-'
score=0

output = (text) ->
  div = $('#output')
  div.html(div.html() + text + '\n')

clear = () ->
  $('#output').html('')

compare = (a, b) ->
  score++
  if a < b then return -1
  if a > b then return 1
  return 0

class Result
  constructor: (@mask, @words) ->
    @count = @words.length

  toString: () ->
    @count + ': ' + @mask + ': ' + @words.join ','

calc = (words, wordLen, maskCount) ->
  words1 = words.filter (w) -> w.length == wordLen
  results = []
  score++
  for mask in genMasks(wordLen, maskCount)
    score++
    calcForMask(words1, mask, results)
  results.sort (r1, r2) -> - compare(r1.count, r2.count)
  return results.filter (r) -> r.count > 1

applyMask = (mask, word) ->
  ((score++;if mask[i] != MASK_CHAR then word[i] else MASK_CHAR) for i in [0..mask.length-1]).join ''

calcForMask = (words, mask, results) ->
  resultMap = {}
  for word in words
    score++
    maskedWord = applyMask(mask, word)
    if not resultMap[maskedWord]?
      resultMap[maskedWord] = [word]
    else
      resultMap[maskedWord].push(word)
  for maskedWord, words of resultMap
    score++
    results.push(new Result(maskedWord, words))

genMasks = (n, m) ->
  score++
  if n < m then return []
  if m == 0
    score++
    if n == 0 then return [[]]
    return [(score++;'x' for i in [1..n])]
  use = genMasks(n - 1, m - 1)
  notUse = genMasks(n - 1, m)
  for x in use
    score++
    x.push(MASK_CHAR)
  for x in notUse
    score++
    x.push('x')
  return use.concat notUse

run = (dictURL, wordLen, maskCount, onStart, onFinish) ->
  $.ajax
    method: 'get'
    url: dictURL
    dataType: 'text'
  .success (data) ->
    output('File size: ' + data.length)
    words = data.match(/[a-z]+/g)
    output('Calculating...')
    onStart(words)
    results = calc(words, wordLen, maskCount)
    onFinish(results)
  .fail (error) ->
    output('Failed to fetch words')
    output('Error: ' + arguments)

profile = (dictURL, wordLen, maskCount) ->
  timer = {}
  clear()
  #...