The idea of simple loaders has already been alluded to, this being that a loader is more useful when it performs a very simple and specific task. This will make testing easier and, as there are so many, they can be chained to others in a more complex usage to perform a greater variety of tasks.
When a single loader is applied to the resource, the loader is called with only one parameter. This is a string containing the content of the resource being loaded.
Synchronous loaders can return a single value representing the transformed module. In more complex cases, the loader can return any number of values by using the following function: this.callback(err, values...).
Errors are then either passed to the function or thrown in a synchronized loader.
In this case, the loader is expected to give back one or two values. The first value is some resulting JavaScript code as a string. The second value is optional and results in a SourceMap and JavaScript object.
Loaders tend to get more complex in situations where they are chained. When discussing complex usage of custom loaders, that would be a good place to start, so let's do that now.