Bundling static assets of builder components

Components developed for the system’s page and form builder features (such as widgets) often require custom CSS and JavaScript assets to achieve their desired appearance and functionality. With complex components, the number of such assets quickly multiplies and it becomes impractical to include them on pages individually. Additionally, keeping request count and static asset size low helps improve page performance. For these reasons, we recommend using bundling and minification for your static assets.

With the Xperience ASP.NET Core integration, use the following process to implement bundling and minification:

  1. Choose and set up a bundling and minification process suitable for your needs and workflow. The implementation and used tools are completely in your hands.
  2. Create bundles for your component assets and provide them to Xperience:
    • Create separate CSS and JS bundles for both the page and form builder.

    • Create separate bundles for the live site and the Xperience administration interface. Certain assets, such as Inline editor registration scripts, are only required when using the page builder editing interface in the administration application and vice versa.

    • The bundles must have specific names, be placed into designated locations within the project’s web root, and contain assets depending on the type of the bundle.

      See the Bundles section for detailed information about the default bundle names and locations.

    • (Optional) If you do not wish to use the default bundle names and locations, you can specify a custom location and sources for individual bundles.

The system includes and renders the resulting bundles on pages where builder functionality is enabled:

  • For the page builder on pages that call the PageBuilderScripts and PageBuilderStyles extension methods.
  • For the form builder automatically when creating forms in the administration interface and on pages containing forms.

Example

The Sample bundling and minification process section demonstrates how to configure a simple bundling process using the Grunt automation library. The process bundles files from the default source directories and places the created bundles into the default locations.

System bundling features

Handling missing bundles

The system stores information about the source directories of files that comprise each bundle. If a bundle is not found in the expected location, files from the corresponding source directories are linked individually as a fallback. Every file results in a new script tag on the page. This can be useful, for example, for debugging purposes in a development environment.

Each bundle has a preconfigured default source directory (listed in the Bundles section). If necessary, you can add additional source directories via provided options classes .

Bundle caching

The system automatically caches served bundles to reduce application overhead.

Note: If your bundling process creates an empty file for a bundle, the given bundle is not included in the page at all. This information is also cached by the system to reduce unnecessary overhead. However, adding content to a previously empty bundle requires clearing of the cache to ensure the change is reflected on the site (e.g., by restarting the application).

Bundles

The following sections describe the default names and locations required for individual bundle types:

Complementary system bundles

For each bundle described below, the system automatically includes a bundle containing the scripts and styles required by the system’s default page and form builder components, such as the Form widget and individual form components. These bundles can be identified by their system prefix and the rules for their inclusion are the same as those for user-created bundles.  

Page builder bundles

Static assets for custom page builder components need to be split between the following types of bundles:

Bundle

Bundle name

Bundle location (~/wwwroot/Content/Bundles/)

Bundle source directory (within ~/wwwroot/)

Bundle contents

Administration scripts

pageComponents.min.js

Admin/pageComponents.min.js

PageBuilder/Admin/**/*.js

Included in the administration interface when editing pages that use the page builder feature.

The bundle must contain inline editor registration and modal dialog configuration scripts together with any other functionality you wish to enable for your components within the page builder editing interface that is not required on the live site.

For a clean directory structure, we recommend placing scripts comprising the bundle into individual directories based on component type. For example:

  • ~/wwwroot/PageBuilder/Admin/InlineEditors/*.js
  • ~/wwwroot/PageBuilder/Admin/ModalDialogs/*.js

Administration styles

pageComponents.min.css

Admin/pageComponents.min.css

PageBuilder/Admin/**/*.css

Included in the administration interface when editing pages that use the page builder feature.

The bundle must contain all styles you wish to use for your components in the page builder editing interface.

For a clean directory structure, we recommend placing styles comprising the bundle into individual directories based on component type. For example:

  • ~/wwwroot/PageBuilder/Admin/Widgets/*.css
  • ~/wwwroot/PageBuilder/Admin/Sections/*.css

Live site scripts

pageComponents.min.js

Public/pageComponents.min.js

PageBuilder/Public/**/*.js

Included on the live site.

The bundle must contain all scripts required for the correct functionality of your custom page builder components on the live site.

For a clean directory structure, we recommend placing scripts comprising the bundle into individual directories based on component type. For example:

  • ~/wwwroot/PageBuilder/Public/Widgets/*.js
  • ~/wwwroot/PageBuilder/Public/Sections/*.js
  • ~/wwwroot/PageBuilder/Public/PageTemplates/*.js

Live site styles

pageComponents.min.css

Public/pageComponents.min.css

PageBuilder/Public/**/*.css

Included on the live site.

The bundle must contain all styles required to correctly display custom components.

For a clean directory structure, we recommend placing styles comprising the bundle into individual directories based on component type. For example:

  • ~/wwwroot/PageBuilder/Public/Widgets/*.css
  • ~/wwwroot/PageBuilder/Public/Sections/*.css
  • ~/wwwroot/PageBuilder/Public/PageTemplates/*.css

Form builder bundles

Static assets for custom form builder components need to be split into three bundles:

  • A bundle containing component scripts.
  • Two bundles containing component styles: one for the live site, the other for the administration interface.

Bundle

Bundle name

Bundle location (within ~/wwwroot/Content/Bundles/)

Bundle source directory (within ~/wwwroot/)

Bundle contents

Administration styles

formComponents.min.css

Admin/formComponents.min.css

FormBuilder/Admin/**/*.css

Included when accessing the form builder editing interface in the administration application.

The bundle must contain all styles you wish to use for your form components in the form builder editing interface in the Xperience administration. Note that the system already attempts to enforce a unified look and feel for components rendered in the form builder interface. See Developing form components and the GetEditorHtmlAttributes extension method.

For a clean directory structure, we recommend placing styles comprising the bundle into individual directories. For example:

  • ~/wwwroot/FormBuilder/Admin/FormComponents/*.css

Administration and live site scripts

formComponents.min.js

Public/formComponents.min.js

FormBuilder/Public/**/*.js

Included on pages with forms and when accessing the form builder editing interface in the administration application.

The bundle must contain all scripts required for the correct functionality of your custom form builder components.

There is no dedicated bundle for the administration interface. This bundle is linked both when rendering forms on the live site (via the Form widget ) and when composing forms via the Form builder interface .

For a clean directory structure, we recommend placing scripts comprising the bundle into individual directories. For example:

  • ~/wwwroot/FormBuilder/Public/FormComponents/*.js

Live site styles

formComponents.min.css

Public/formComponents.min.css

FormBuilder/Public/**/*.css

Included on pages with forms.

The bundle must contain all styles required to correctly display your form builder components on the live site.

For a clean directory structure, we recommend placing styles comprising the bundle into individual directories. For example:

  • ~/wwwroot/FormBuilder/Public/FormComponents/*.css

Sample bundling and minification process

The following examples demonstrates a bundling and minification process using Node.js with the Grunt JavaScript task runner for automation. The process cleans all output directories, concatenates the CSS and JS files from relevant directories, performs minification, and outputs the resulting bundles into the default directories expected by the system. 

The process uses the following Grunt plugins:

  • grunt-contrib-clean for output directory cleaning
  • grunt-contrib-concat for bundling
  • grunt-contrib-cssmin for CSS minification
  • grunt-terser for JavaScript minification

First, add Grunt together with the plugins to your project’s package.json file, create a new Gruntfile.js and load the plugins:

Gruntfile.js



module.exports = function (grunt) {

    // Loads required Grunt plugins
    grunt.loadNpmTasks('grunt-contrib-clean');
    grunt.loadNpmTasks('grunt-contrib-concat');
    grunt.loadNpmTasks('grunt-contrib-cssmin');
    grunt.loadNpmTasks('grunt-terser');

    grunt.initConfig({
        // Contains task definitions
    });
}


Define the tasks used to create the individual bundles:

Gruntfile.js



grunt.initConfig({

        // Cleans all bundle output directories
        clean: {
            formBuilder: ['wwwroot/Content/Bundles/Public/formComponents.css', 'wwwroot/Content/Bundles/Public/formComponents.min.css', 'wwwroot/Content/Bundles/Admin/formComponents.css',             
                          'wwwroot/Content/Bundles/Admin/formComponents.min.css', 'wwwroot/Content/Bundles/Public/formComponents.js', 'wwwroot/Content/Bundles/Public/formComponents.min.js'],

            pageBuilder: ['wwwroot/Content/Bundles/Public/pageComponents.css', 'wwwroot/Content/Bundles/Public/pageComponents.min.css', 'wwwroot/Content/Bundles/Admin/pageComponents.css', 
                          'wwwroot/Content/Bundles/Admin/pageComponents.min.css', 'wwwroot/Content/Bundles/Public/pageComponents.js', 'wwwroot/Content/Bundles/Public/pageComponents.min.js', 
                          'wwwroot/Content/Bundles/Admin/pageComponents.js', 'wwwroot/Content/Bundles/Admin/pageComponents.min.js']
        },

        // Bundles files from the default source directories
        concat: {
            formBuilder: {
                files: {
                    // Styles for the live
                    'wwwroot/Content/Bundles/Public/formComponents.css': ['wwwroot/FormBuilder/Public/**/*.css'],
                    // Styles for the administration interface
                    'wwwroot/Content/Bundles/Admin/formComponents.css': ['wwwroot/FormBuilder/Admin/**/*.css'],
                    // Scripts included on both the live site and administration 
                    'wwwroot/Content/Bundles/Public/formComponents.js': ['wwwroot/FormBuilder/Public/**/*.js']
                }
            },
            pageBuilder: {
                files: {
                    // Styles for the live site
                    'wwwroot/Content/Bundles/Public/pageComponents.css': ['wwwroot/PageBuilder/Public/**/*.css'],
                    // Styles for the administration interface
                    'wwwroot/Content/Bundles/Admin/pageComponents.css': ['wwwroot/PageBuilder/Admin/**/*.css'],
                    // Scripts for the live site
                    'wwwroot/Content/Bundles/Public/pageComponents.js': ['wwwroot/PageBuilder/Public/**/*.js'],
                    // Scripts for the administration
                    'wwwroot/Content/Bundles/Admin/pageComponents.js': ['wwwroot/PageBuilder/Admin/**/*.js']
                }
            }
        },

        // Minifies the resulting CSS bundles
        cssmin: {
            formBuilder: {
                files: {
                    'wwwroot/Content/Bundles/Public/formComponents.min.css': 'wwwroot/Content/Bundles/Public/formComponents.css',
                    'wwwroot/Content/Bundles/Admin/formComponents.min.css': 'wwwroot/Content/Bundles/Admin/formComponents.css'
                }
            },
            pageBuilder: {
                files: {
                    'wwwroot/Content/Bundles/Public/pageComponents.min.css': ['wwwroot/Content/Bundles/Public/pageComponents.css'],
                    'wwwroot/Content/Bundles/Admin/pageComponents.min.css': ['wwwroot/Content/Bundles/Admin/pageComponents.css']
                }
            }
        },

        // Minifies the resulting JS bundles
        terser: {
            formBuilder: {
                files: {
                    'wwwroot/Content/Bundles/Public/formComponents.min.js': ['wwwroot/Content/Bundles/Public/formComponents.js']
                }
            },
            pageBuilder: {
                files: {
                    'wwwroot/Content/Bundles/Public/pageComponents.min.js': ['wwwroot/Content/Bundles/Public/pageComponents.js'],
                    'wwwroot/Content/Bundles/Admin/pageComponents.min.js': ['wwwroot/Content/Bundles/Admin/pageComponents.js']
                }
            }
        }
});


Finally, register alias tasks that batch the created tasks for convenience:

Gruntfile.js



// Running the 'formBuilder' and 'pageBuilder' tasks automatically executes all tasks provided in the array
grunt.registerTask('formBuilder', ['clean:formBuilder', 'concat:formBuilder', 'cssmin:formBuilder', 'terser:formBuilder']);
grunt.registerTask('pageBuilder', ['clean:pageBuilder', 'concat:pageBuilder', 'cssmin:pageBuilder', 'terser:pageBuilder']);


Using the created tasks, you can automate the bundling and minification process for your static assets. 

Customizing bundle locations and sources

You can customize the location where Xperience expects bundles and their contents (to be linked individually, in case the bundle is not found). For this purpose, the system provides the PageBuilderBundlesOptions and FormBuilderBundlesOptions options classes. 

The options classes expose properties that allow you to configure the corresponding bundle:

These properties can be configured using the conventional ASP.NET Core options pattern. See the following sections for details about specific configuration options:

Changing the bundle location and name

You can change the file name and path where the system looks for individual bundles. Set the WebRootBundlePath property provided by the individual bundle objects within the options classes.

For example, the following code demonstrates how to set a custom location for the FormBuilderPublicStyles bundle via the ConfigureServices method within the application’s startup class:

Application's startup class



public void ConfigureServices(IServiceCollection services)
{
    services.Configure<FormBuilderBundlesOptions>(options =>
    {

        // Sets a custom path to the style sheet bundle used by form builder components on the live site
        // The specified path implicitly starts from the application's web root (~/wwwroot/ folder)
        options.FormBuilderPublicStyles.WebRootBundlePath = "My/Custom/Bundle/Path.min.css";
    });

    ...
}


Adding source directories for a bundle

If the default directories (described in the page and form builder bundles sections) are not sufficient, bundle options allow you to add additional sources for each bundle. The system links all files from the specified directories if a corresponding bundle is not found. See Handling missing bundles.

Add new directories to the list provided via the Contents.IncludedWebRootDirectories property of the bundle you wish to configure.  

For example, the following code adds ~/wwwroot/Some/Directory to the list of sources for the bundle of page builder component styles linked on the live site. If the corresponding bundle is not found, the system includes the CSS files from the specified directory as well:

Application's startup class



public void ConfigureServices(IServiceCollection services)
{
    services.Configure<PageBuilderBundlesOptions>(options =>
    {
        // Adds a source directory for the bundle of styles used by page builder components on the live site
        // All added paths implicitly start from the application's web root (~/wwwroot/ folder)
        options.PageBuilderPublicStyles.Contents.IncludedWebRootDirectories.Add("Some/Directory");
    });

    ...
}


Replacing the default form builder jQuery libraries

By default, the system links two jQuery bundles used in the form and page builder and their default components:

  • jquery-3.5.1.js
  • jquery.unobtrusive-ajax.js

You can disable the use of jQuery for the form and page builder by setting the CMSBuilderScriptsIncludeJQuery key to false in the configuration file of your live site project (appsettings.json or web.config). All default features remain functional without jQuery.

If you wish to use jQuery, but require a different version, you need to specify the version for both form builder and page builder.

Form builder

Specify the path to your custom jQuery bundles using the

  • JQueryCustomBundleWebRootPath
  • JQueryUnobtrusiveAjaxCustomBundleWebRootPath

properties of the FormBuilderBundlesOptions object:

Application's startup class



public void ConfigureServices(IServiceCollection services)
{
    services.Configure<FormBuilderBundlesOptions>(options =>
    {
        // Specifies custom jQuery bundles to link instead of the system defaults
        // All paths implicitly start from the application's web root (~/wwwroot/ folder)
        options.JQueryCustomBundleWebRootPath = "Path/To/My/jQuery/Bundle";
        options.JQueryUnobtrusiveAjaxCustomBundleWebRootPath = "Path/To/My/jQueryUnobtrusive/Bundle";
    });

    ...
}


When you set a custom path using one of these properties, the system no longer links the bundle in the page builder and you need to manually link your custom bundles as described below.

Page builder

Specify the path to your custom jQuery bundles in the layout of your pages:

Page layout



<script src="Path/To/My/jQuery/Bundle"></script>
<script src="Path/To/My/jQueryUnobtrusive/Bundle"></script>