Upward and onward to greater glory!
JavaScript (ES6), 58 54 bytes



a=&gt;a.map(c=&gt;a&lt;(a=c)?`Excelsior${s+='!'}
`:s='').join``


Try it online!

Commented

a =&gt;                           // a[] = input array, also used to store the previous value
  a.map(c =&gt;                   // for each value c in a[]:
    a &lt;                        //   compare the previous value
    (a = c)                    //   with the current one; update a to c
                               //   this test is always falsy on the 1st iteration
    ?                          //   if a is less than c:
      `Excelsior${s += '!'}\n` //     add a '!' to s and yield 'Excelsior' + s + linefeed
    :                          //   else:
      s = ''                   //     reset s to an empty string and yield an empty string
  ).join``                     // end of map(); join everything


Why re-using a[ ] to store the previous value is safe

There are three possible cases:


If \$a[\text{ }]\$ is empty, the callback function of .map() is not invoked at all and we just get an empty array, yielding an empty string.
If \$a[\text{ }]\$ contains exactly one element \$x\$, it is coerced to that element during the first (and unique) test a &lt; (a = c). So, we're testing \$x &lt; x\$, which is falsy. We get an array containing an empty string, yielding again an empty string.
If \$a[\text{ }]\$ contains several elements, it is coerced to NaN during the first test a &lt; (a = c). Therefore, the result is falsy and what's executed is the initialization of \$s\$ to an empty string -- which is what we want. The first meaningful comparison occurs at the 2nd iteration.