Showing posts with label CSS. Show all posts
Showing posts with label CSS. Show all posts

Saturday, November 27, 2021

Sitecore SXA 10.2 Improvements: CSS and JS Source Maps

It is a copy of my article initially placed here to keep all things in one place.

Remember my article about enabling of Source Map for Sitecore SXA?

November 2021 Sitecore SXA 10.2 was released. But if you check the list of new features/improvements in release notes, you will not find one important improvement for developers: Now enabling CSS Source Maps becomes much easier!

Steps that you need:

  1. Open gulp\config.js file
  2. Set sassSourceMap value to true
  3. Set js>JsSourceMap value to true
  4. Set css>cssSourceMap value to true
  5. Restart gulp

If you will look inside SXA Source Maps implementation, you will see that implementation is very similar to what I did for earlier Sitecore versions. Probably SXA Developers Team was inspired by my previous post.

Enjoy your work on the SXA theme with enabled source maps! And remember, if you need to get the source maps for Sitecore SXA earlier 10.2 version, you can use this article.

Thursday, July 1, 2021

Sitecore SXA: Turning on CSS Source Map Files

It is a copy of my article, initially posted here to keep all things in one place.

Update from 26 November 2021: If you use Sitecore 10.2 version, please use out of the box configuration. Or you can consider use @sca/celt 10.2 for your SXA theme for lower Sitecore versions as well.

CSS source map files are the cornerstone thing for modern web development. Sitecore SXA has the ability to turn on the usage of CSS map files. But there are use cases when you could not turn on SXA source maps out of the box. Let's examine why do you need to have source map files and how to configure using them for different cases in the Sitecore experience accelerator.

Theory: CSS Source Map Files

If you are familiar with CSS source map files, you can skip this chapter and scroll to the next one. It is present here because there will be parts that rely on source map specification in further explanation.

Source maps were introduced a long time ago. It was always easier for developers to save files with proper formatting. But proper formatting takes precious bytes. Each space, tab, line end character, clear variable name make your file bigger. And each additional byte makes page speed slower and users' experience worse. And developers found a way how to solve this problem. You can post-process your source and get rid of everything that doesn't affect styles and JS code execution. That is how minification was introduced. But minification created another problem. You made your page fast, but you also made troubleshooting the frontend much harder. At this point source files mapping appeared. The idea was pretty simple, we can provide a map from minified file to its real sources. And when you open sources in your developer's toolbar you will see a reference to your source file and it will allow you to save pricey seconds during troubleshooting of issues.

Web development evolved and new meta-languages and approaches appeared. SASS, SCSS, Babel, CoffeeScript, TypeScript opened new abilities for developers and made development faster and more comfortable. But browsers didn't evolve fast. And you still can use only plain CSS and JS for your pages. (Fortunately new languages specifications, but still only this approaches). And source map files became even more valuable as you write code in one meta-language, but browsers use a different one. Because browser can pretty print page sources, but it has no idea how file was written initially.

We used that source map has somename.css.map file name. But that is actually not a standard. It is a common location, where the source map is placed. But it could be configured! We can specify sourceMappingURL parameter at the end of .css file.

We can use different sourceMappingURL parameter values:

  • Absolute link to source map file
  • Relative link to source map file
  • Data URL: base64 encoded source map file. sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW.....

Another option, how to specify the source map file is setting X-SourceMap (or SourceMap in the newer specification) header. It will require changes in server-side code or configuring rewrite rules, which will make our solution more complex.

Adding CSS Source Map when Minification is Disabled

SXA Creative Live Exchange allows working in different modes. One of the options is to upload all produced .css files to Sitecore Media Library. (When you have enableMinification: true and disableSourceUploading: true settings in your config.js file.

In this case, everything is easy. You need to open your config.js file and change cssSourceMap and sassSourceMap values to true. In this case, SXA gulp scripts will add source maps to the end of each .css file of your theme.

Challenges of Adding CSS Source Map when Minification is Enabled

When minification is enabled, SXA gulp scripts prepare pre-optimized-min.css file, which is uploaded to Sitecore Media Library and then served as a styles file for your SXA theme.

With enabled cssSourceMap and sassSourceMap values, SXA gulp scripts will start to create pre-optimized-min.css.map source map file. But there is no watcher for this type of file. Also, we will need to have item pre-optimized-min.css with .map extension in Sitecore Media Library. Or pre-optimized-min item with .css.map extension. But Sitecore doesn't accept dots in item names by default. And we will not be able to use .css.map extension because in this case we will need to have 2 Sitecore items in the same level, which is also not allowed. But from the theory part, we know that we can add source map directly inside .css or configure different source map file name. Let's do it!

Placing Source Map inside pre-optimized-min.css

If we will look at how minification is done in SXA gulp scripts we will notice that there it uses gulp-sourcemaps npm package. And this package allows the flexible configuring location of source maps.

All that we need is to open \node_modules\@sxa\celt\util\cssMinificator.js file, which is responsible for concatenation and minification of .css files, and patch it. (Of course, it is better to copy this file to your source control and modify it separately. I am writing about changing in this file only for simplification). We need to replace gulpSourcemaps.write('.\') on gulpSourcemaps.write() . Changing of argument will write source map inside pre-optimized-min.css. It is a quick and easy change. But it could be used only for development environments, but not for production. Because it will double the size of css file and we will lose all improvements added by minification.

Proper Way of Adding CSS Source Map File to Your SXA Theme

We have already reviewed few options, how we can add source map files. But that options don't work for production. It means that you will need to have different approaches for different environments. It is not good. Let's figure out how to do it in a long way, but properly.

First of all, similar to other approaches, we need to set cssSourceMap and sassSourceMap values to true in config.js file.

To avoid problems with dots in item names and the same filenames on the same level, let's name our source maps filename pre-optimize-min-css.map instead of pre-optimized-min.css.map . This small change will make our work easier. We will not need to have any changes in the Sitecore backend or configuration. To achieve it, let's copy cssMinificator.js file and make small change:

// tasks\override\cssMinificator.js
// File is based on \node_modules\@sxa\celt\util\cssMinificator.js
// It allows to use different name for map file
// For our case we need [file]-css.map instead [file].css.map

const gulp = require('gulp');
const cleanCSS = require('gulp-clean-css');
const path = require('path');
const gulpSourcemaps = require('gulp-sourcemaps');
const gulpif = require('gulp-if');
const gulpConcat = require('gulp-concat');
const config = require(path.resolve(process.cwd(), './gulp/config'));

module.exports = function (cb) {
    let conf = config.css;
    if (!conf.enableMinification) {
        if (process.env.debug == 'true') {
            console.log('CSS minification is disabled by enableMinification flag'.red)
        }
        return cb();
    }
    let streamSource = conf.minificationPath.concat(['!' + conf.cssOptimiserFilePath + conf.cssOptimiserFileName])
    let stream = gulp.src(streamSource)
        .pipe(gulpif(function () {
            return conf.cssSourceMap;
        }, gulpSourcemaps.init()))
        .pipe(cleanCSS(config.minifyOptions.css))
        .pipe(gulpConcat(conf.cssOptimiserFileName))
        //Changed part: we use different filename suffix for Sitecore compatibility
        .pipe(gulpif(function () {
            return conf.cssSourceMap;
        }, gulpSourcemaps.write('./', {
            mapFile: function (mapFilePath) {
                // source map files are named *-css.map instead of *.css.map
                return mapFilePath.replace('.css.map', '-css.map');
            }
        })))
        //Disable previous pipe and enable this one if you want to add CSS source maps to the same file
        //.pipe(gulpif(function () {
        //  return conf.cssSourceMap;
        //}, gulpSourcemaps.write()))
        .pipe(gulp.dest('styles'));
    stream.on('end', function () {
        console.log('Css minification done'.grey)
    });
    return stream

}

watchCSS built-in task has a dependency on cssMinificator.js. That is why we need also to override it and use our new minification script.

// tasks\override\watchCss.js
// File is based on \node_modules\@sxa\celt\util\watchCss.js
// We need to call different CSS optimizer with changed map file name

const gulp = require('gulp');
const colors = require('colors');
const vinyl = require('vinyl-file');
const config = require(global.rootPath + '/gulp/config');
//Changed relative path to absolute
const { fileActionResolver } = require('@sxa/celt/util/fileActionResolver');
//Changed path to overridden module
const cssMinificator = require('./cssMinificator');

module.exports = function watchCss() {
    setTimeout(function () {
        console.log('Watching CSS files started...'.green);
    }, 0);
    let conf = config.css,
        indervalId,
        watch = gulp.watch(conf.path, { queue: true });
    watch.on('all', function (event, path) {
        var file = {
            path: path
        };
        if (event !== 'unlink') {
            file = vinyl.readSync(path);
        }

        file.event = event;
        if (!conf.disableSourceUploading || file.path.indexOf(conf.cssOptimiserFileName) > -1) {
            fileActionResolver(file);
        } else {
            if (process.env.debug == 'true') {
                console.log(`Uploading ${file.basename} prevented because value disableSourceUploading:true`.yellow);
            }
        }
        if (conf.enableMinification && file.path.indexOf(conf.cssOptimiserFileName) == -1) {
            indervalId && clearTimeout(indervalId);
            indervalId = setTimeout(function () {
                indervalId = clearTimeout(indervalId);
                cssMinificator();
            }, 400)
        }
    })
}

The next step is adding watch and upload tasks for .map files:

// tasks\watchMap.js
const gulp = require('gulp');
const vinyl = require('vinyl-file');
const config = require(global.rootPath + '/gulp/config');
//Changed relative path to absolute
const { fileActionResolver } = require('@sxa/celt/util/fileActionResolver');

module.exports = function watchMap() {
    setTimeout(function () {
        console.log('Watching MAP files started...'.green);
    }, 0);
    let conf = config.map
        watch = gulp.watch(conf.path, { queue: true });
    watch.on('all', function (event, path) {
        var file = {
            path: path
        };
        if (event !== 'unlink') {
            file = vinyl.readSync(path);
        }

        file.event = event;
        fileActionResolver(file);
    })
}
// tasks\uploadMap.js
const gulp = require('gulp');
const tap = require('gulp-tap');
const config = require(global.rootPath + '/gulp/config');
//Changed relative path to absolute
const { fileActionResolver } = require('@sxa/celt/util/fileActionResolver');

module.exports = function uploadMap() {
    var conf = config.map;
    const promises = [];

    return gulp.src(conf.path)
        .pipe(tap(
            function (_file) {
                let file = _file;
                file.event = 'change';
                promises.push(() => fileActionResolver(file));
            })
        )
        .on('end', async () => {
            for (const prom of promises) {
                await prom();
            }
        })
}

These files need configuration, where to look for .map files. We configure it in the additional setting in config.js file:

// config.js

...
map: {
        path: ['styles/*.map']
    },
...

Now, let's bring it all together in gulpfile.js

// gulpfile.js

...
const watchCssTasks = require('./gulp/tasks/override/watchCss');
// Instead of:
// const watchCssTasks = getTask('watchCss');

...

const cssOptimizationTasks = require('./gulp/tasks/override/cssMinificator');
// Instead of:
// const cssOptimizationTasks = getTask('cssOptimization');

...

// New tasks for .map files
const watchMapTasks = require('./gulp/tasks/watchMap');
const uploadMapTasks = require('./gulp/tasks/uploadMap');
module.exports.watchMap = gulp.series(login, watchMapTasks);
module.exports.uploadMap = gulp.series(login, uploadMapTasks);

...

// Changed default task with added watchMapTasks
module.exports.default = module.exports.watchAll = gulp.series(login,
    gulp.parallel(
        watchHtmlTasks,
        watchCssTasks,
        watchJSTasks,
        watchESTasks,
        watchImgTasks,
        watchScribanTasks,
        watchSassTasks.watchStyles,
        watchSassTasks.watchBase,
        watchSassTasks.watchComponents,
        watchSassTasks.watchDependency,
        watchSassSourceTasks,
        watchMapTasks
    )
);

...

// Extended Build + upload tasks
module.exports.rebuildAll = gulp.series(
    login,
    jsOptimizationTasks, sassComponentsTasks, cssOptimizationTasks,
    uploadJsTasks, uploadCssTasks, uploadImgTasks, uploadMapTasks
)
module.exports.rebuildMain = gulp.series(
    login,
    jsOptimizationTasks, sassComponentsTasks, cssOptimizationTasks,
    uploadJsTasks, uploadCssTasks, uploadMapTasks
)

...

Voila! Now after running gulp buildAll or triggering watch task we get a proper link at the end of pre-optimize-min.css and pre-optimize-min-css.map file itself. And both these files are uploaded to the media library. Now when you troubleshoot any CSS issues using the developer tools, you see, where it is done in sources.

Conclusion

I hope that someone from the Sitecore SXA team will read my article and include the ability to upload CSS source map files for pre-optimize-min.css out of the box with SXA gulp scripts. It is easy to change, but it can save a lot of developer hours.

But even if not, you still have control over your sources and your project. And if you need something to be improved then everything is in your hands! No needs to wait when it will be implemented by someone else.

Saturday, February 22, 2020

Sitecore and Google Lighthouse Integration

Google Lighthouse became almost industry standard tool for measuring performance, accessibility, progressive web apps and SEO. There are few reasons, why it had happened. First of all, it is integrated with most popular browser: Google Chrome. Everyone is able to press F12(open DevTools) click “Generate report” button and get results. You should not be an expert, Lighthouse will tell you if your images are not optimized, if your scripts running too long, if you have wrong page structure that have bad influence on SEO. Second reason of Lighthouse popularity is that Google is number one web search engine. And as it is major source of traffic for many websites, it has sense to optimize your pages based on Google recommendations, which are can easily get from Lighthouse.

Pages performance, accessibility and SEO are very important for any website built on Sitecore. That is why I decided to integrate Lighthouse with Sitecore. It will allow you to run and access reports directly from Sitecore interface, have historical information and historical charts. As for me, it could be good checker for your day to day work with Sitecore. You are doing some improvement, you can easily understand how this improvement influence on particular pages and whole website. Or you are doing some change that influences on many pages, you can easily find out if it doesn’t break anything related to performance or accessibility.

I am glad to introduce: Sitecore.Lighthouse. Sitecore module that provides ability you to get everything from Lighthouse directly from Sitecore interface. On 22 February of 2020 it was tested only on Habitat Home and other few sites. That is why any contribution or bug reports are welcome. 



Sunday, December 9, 2018

CSS Grid Layout for Sitecore

It is copy of my article to keep everything in one place, initially published here.

Before reading this blog, I recommend familiarising yourself with CSS Grid Layout. I like this guide. Or if you prefer to learn it during play in game, try CSS Grid Garden.

Major part of Sitecore projects from my experience has either predefined preset of layouts(containers for components) or are based on basic components of CSS frameworks: rows and columns(e.g.: Bootstrap, Foundation and others).

First approach is clear and obvious, you have predefined list of possible layouts: single column, two equal columns, three equal columns, one narrow column one wide column and others. This approach works good, but when site editor would need some new layout, he would not be able to create it without help of a developer. After passing of some time you will get a lot of containers that are designed only to build grid system. And when you are creating new page it is hard to select right container control from bunch of “three column layouts”.

Second approach is more flexible. You have 2 generic controls: Row and Column. You can define column width, row height. Exactly, like you do with Bootstrap grid system. Difference is only that you set column and row parameters on the level of rendering parameters in Sitecore. By combination of different rows and columns, you would be able to get any layout you can imagine. And you have only two controls that respond for grid structure: row and column. You know exactly what they do and how they will behave in different situations. The back side of coin is difficulties in designing complex layout. You get dozen of rows and columns. You are able to manage them somehow in the Experience Editor, making time to time mistakes with adding components to the wrong placeholder. And you get mess in the layout details of your page. You see bunch of rows and columns in the list with absence of understanding what is inside of them and are they still in use?

Mixed path of first and second approaches can resolve some of their limitations. But misuse of it by site managers could create a mess.

CSS Grid layout offers a grid-based layout system, with rows and columns, making it easier to design web pages without having to use floats and positioning. It is supported by modern browsers:

css-grid-layout-for-sitecore-header-1

sing CSS Grid allow you to get column/row layout without needs to include any additional frameworks. You can have only one container component per page in Sitecore. This component is configurable, you can set any amount of columns and rows, spacing between columns and rows, alignment.

I have created prototype of CSS Grid Layout for Sitecore.

You can try it by yourself:

  1. Download Sitecore update package from AppVeyor
  2. Install it using update installation wizard /sitecore/admin/UpdateInstallationWizard.aspx
  3. You will be able to insert "Container" component /sitecore/layout/Renderings/CSSGrid/Container. Amount of columns and rows is configurable using rendering parameters.
  4. Inside placeholder under "Container" component you will be able to insert any amount of "Item" components /sitecore/layout/Renderings/CSSGrid/Item. Location(row/column) and size(rows/columns) of “Item” component is configurable using rendering parameters (It is expected that all UI components will be compatible with Grid CSS and you will be able to place them inside container. And configure location/size/alignment using rendering parameters)
If you have any difficulties, refer to example how to use it.

Few screenshots, how it will work:
css-grid-layout-for-sitecore-2
css-grid-layout-for-sitecore-3
css-grid-layout-for-sitecore-4

Using CSS Grid Layout for Sitecore we have got flexible layout for Sitecore project without mess in layout details. We have only one container for unlimited number of controls. Location of each control is configured by its rendering parameters. In the next part I will describe how to use this module in details.

P.S. If you are interested in free automated system that prepares Sitecore CSS Grid Layout package from GitHub sources, please read my article about possible CI/CD configuration for open source projects.