CodePen supports seven different preprocessors at this time. Each one can error in an infinite number of ways while you're creating a Pen. For instance, you reference a variable that isn't defined in Sass or used a reserved keyword in CoffeeScript. We wanted error messages that were informative but stayed out of your way while you code.

The Old Way

We knew that we needed to let users know if they had a preprocessing error rather than just ignore it. When you compile in any of these languages and there is an error, you get error messaging back but no compiled output. So we thought "Well, we can't show the result because we don't have any output to show." And instead, we used that result area to show the error itself.

It became know as the "red screen of death."

red-screen-of-death

Little use interface hot tip for you: if a screen becomes dubbed anything "of death" it is a good cue to re-think that screen.

The New Way (Iteration #2)

We decided to unify the look of the errors and present them inline with your code. The idea literally came from a CodePen user Matthew Leon who wrote to us:

When I'm coding SCSS, I'll often stop typing for a second, leaving the SCSS in a state that won't compile. Instead of displaying the angry red "Preprocessing Error" screen, I should just get a little angry alert icon somewhere in the corner of the SCSS pane, and the bottom pane should continue to display my Pen in its last "good" state.

It ended up like this:

Error Messages

Then instead of displaying the red screen of death, we just didn't change the result area at all, leaving it in its last-known, error-free state. We took it from error messages that stop you in your tracks to messages that nudge you in the right direction.

To make this happen, CodePen's errors had to get a lot smarter. We needed to know the error message and exactly what line it occurred on.

The Preprocessor Service

Processing of the CodePen preprocessors is handled by a service aptly named The Preprocessor Service. It's a cluster of web servers that handle processing requests from the CodePen editor. Separating this task as a service makes it secure and performant. It's exposed as an internal JSON API. Below is the actual Ruby code we execute to capture errors thrown by the libraries that actually execute your code.

def preprocess(syntax, markup)
  begin
    pps = PreProcessorService.new

    Timeout.timeout(TimeUtil.MAX_TIMEOUT) do
      clean_markup = clean_raw_markup(syntax, markup)
      return self.send(syntax, clean_markup)
    end

  rescue Haml::SyntaxError, Haml::Error => e
    raise PreprocessorException.new(e.message, syntax, markup, e.line)
  rescue Slim::Parser::SyntaxError => e
    raise PreprocessorException.new(e.message, syntax, markup, e.lineno)
  rescue Sass::SyntaxError => e
    # Since we preprend an import, we substract the line we prepended
    raise PreprocessorException.new(e.message, syntax, markup, e.sass_line - 1)
  rescue TimeoutError => e
    msg = Copy.t("request_timed_out") % {:syntax => FriendlyMeta.pretty_type(syntax)}
    raise PreprocessorException.new(msg, syntax, markup, '1')
  rescue SyntaxError => e
    raise PreprocessorException.new(e.message, syntax, markup, '1')
  rescue PreprocessorException => e
    # PreprocessorExceptions returned from Node.js service
    raise e
  rescue Exception => e
    msg = Copy.t("invalid_syntax") % {:syntax => FriendlyMeta.pretty_type(syntax)}
    raise PreprocessorException.new(msg, syntax, markup, '1')
  end
end

The most complex part of the code is the error handling. Sadly there is no standard way to get the line number so we had to capture every exception individually and pull the line number from that. We reviewed the reference documentation for every project to ensure we're capturing every possible error. If all else fails and we don't have a line number at all we'll still provide you the error message on line number one.

Displaying the Errors

Since we use CodeMirror as our editor we get to use line widgets to add a custom HTML element right below any line.

var newError = CodeMirrorEditor.addLineWidget(lineNum, HTMLtemplate, options);
allErrors.push(newError);

That CodeMirror API adds the widget right inside the editor while maintaining all the regular functionality (e.g. the mouse cursor will skip right past the error if you arrow key up and down around it). We save what the function returns because you need it to remove the error (or errors) later once the error is fixed.

The HTML template we pass in is built from data we get from the JSON response from the Preprocessor Service.

The New Way (Iteration #3)

While the inline errors were a big upgrade in functionality, some users actually liked it less. The problem was rooted in the fact that we were essentially messing with their code. Not changing it, but pushing down lines to show the error. This felt abrupt and sometimes worse than the red screen of death.

The fix this time was to simply draw attention to the line with the error by highlighting it and displaying a warning icon. If they need to know more about the error, it can be clicked to show the message, which is hidden by default. The template is essentially built like this. Shown here with jQuery, but you could template however makes sense for your project:

var HTMLtemplate = 
$("
" + "
" + message + "
" + "!" + "
")[0];

When the little span.inline-error-toggle is clicked (some simple event delegation) we toggle out some classes to display actual error message.

error-rejigger

Wrap Up

Our error messages have really evolved. The red screen of death was good enough, but barely. Our second iteration was better, but quickly made some users feel like we'd taken a step back because we we're jumping into the editor while you were typing. The final version really feels right.

Thanks to the CodePen users who told us what they liked and disliked we were able to craft a much better solution. CodePen is better for it.