Custom pre processing

Shannon Deminick edited this page Oct 13, 2017 · 6 revisions

Custom pre-processing pipeline

It's easy to customize how your files are processed. This can be done at a global/default level, at the bundle level or at an individual file level.

Each processor is of type Smidge.FileProcessors.IPreProcessor which contains a single method: Task ProcessAsync(FileProcessContext fileProcessContext, Func<string, Task> next);. A pre-processor works like middleware and that it will need to call the next one in the chain, this allows a pre-processor to execute before and after other pre-processors if necessary.

The built-in processors are:

  • CssImportProcessor
  • CssUrlProcessor
  • CssMinifier
  • JsMinifier (JsMin)
  • NuglifyCss
  • NuglifyJs
  • UglifyNodeMinifier

But you can create and add your own just by adding the instance to the IoC container, for example if you created a dotless processor::

services.AddScoped<IPreProcessor, DotLessProcessor>();

Global custom pipeline

If you want to override the default processing pipeline for all files, then you'd add your own implementation of Smidge.FileProcessors.PreProcessPipelineFactory to the IoC container after you've called AddSmidge();, like:

services.AddSingleton<PreProcessPipelineFactory, MyCustomPreProcessPipelineFactory>();

and override the GetDefault method. You can see the default implementation here: https://github.com/Shazwazza/Smidge/blob/master/src/Smidge/FileProcessors/PreProcessPipelineFactory.cs

Alternatively in Smidge 2.0 you can specify a callback in options to modify the default pipeline, for example:

services.AddSmidge(_config)
    .Configure<SmidgeOptions>(options =>
    {
        options.PipelineFactory.OnGetDefault = GetDefaultPipelineFactory;
    });

The GetDefaultPipeline method could do something like:

/// <summary>
/// A callback used to modify the default pipeline to use Nuglify for JS processing
/// </summary>
/// <param name="fileType"></param>
/// <param name="processors"></param>
/// <returns></returns>
private static PreProcessPipeline GetDefaultPipelineFactory(WebFileType fileType, IReadOnlyCollection<IPreProcessor> processors)
{
    switch (fileType)
    {
        case WebFileType.Js:
            return new PreProcessPipeline(new IPreProcessor[]
            {
                processors.OfType<NuglifyJs>().Single()
            });                
    }
    //returning null will fallback to the logic defined in the registered PreProcessPipelineFactory
    return null;
}

Individual file custom pipeline

If you want to customize the pipeline for any given file it's really easy. Each registered file is of type Smidge.Models.IFile which contains a property called Pipeline of type Smidge.FileProcessors.PreProcessPipeline. So if you wanted to customize the pipeline for a single JS file, you could do something like:

@inject Smidge.FileProcessors.PreProcessPipelineFactory PipelineFactory

@{ Smidge.RequiresJs(new JavaScriptFile("~/Js/test2.js")
        {
            Pipeline = PipelineFactory.GetPipeline(
                //add as many processor types as you want
                typeof(DotLess), typeof(JsMin))
        })

Bundle level custom pipeline

If you want to customize the pipeline for a particular bundle, you can just create your bundle with a custom pipeline like:

services.AddSmidge()
    .Configure<Bundles>(bundles =>
    {                   
        bundles.Create("test-bundle-3", 
            bundles.PipelineFactory.GetPipeline(
                //add as many processor types as you want
                typeof(DotLess), typeof(JsMin)), 
            WebFileType.Js, 
            "~/Js/Bundle2");
    });

There are quite a few overloads for creating bundles with custom pipelines.

Minification benchmarks

With Smidge 2.0 there are a couple of minification pre-processor options. By default Smidge will use JsMin but you could swap this for Nuglify or Uglify (via NodeServices) if you chose to. This decision would depend on if you wanted some of the more advanced features of either of these engines. It's worth seeing the benchmarks of these minification engines because Smidge executes at runtime so in theory you'd want it to execute as fast as possible. Smidge does a file based cache so once the files are processed for a given version they don't get re-processed on the next app restart (i.e. the processing only happens once). (NOTE: a smaller Minified % is better)

Method Median StdDev Scaled Scaled-SD Minified % Gen 0 Gen 1 Gen 2 Bytes Allocated/Op
JsMin 10.2008 ms 0.3102 ms 1.00 0.00 51.75% - - - 155,624.67
Nuglify 69.0778 ms 0.0180 ms 6.72 0.16 32.71% 53.00 22.00 15.00 4,837,313.07
JsServicesUglify 1,548.3951 ms 7.6388 ms 150.95 3.73 32.63% 0.97 - - 576,056.55

The last benchmark may be a bit misleading because the processing is done via NodeJs which executes in a separate process so I'm unsure if the actual memory usage of that can be properly captured by BenchmarkDotNet but you can see it's speed is much slower.