Supporting Sass Variables in XM Cloud and Storybook

Use Sitecore XM Cloud’s Sass plugin and Storybook’s Sass processing options to resolve image path issues

November 5, 2024

By Kelly Parks

Legacy Sass: Challenges in Upgrading to Sitecore XM Cloud

When upgrading a Sitecore XP solution to Sitecore XM Cloud, there are often unexpected challenges. We faced such a hurdle when we were migrating our client’s custom Sass files, which contained paths to SVG icons. These paths were hardcoded in the Sass files.

&.icon-download {
      content: url(/img/acmecorp/icons/icon_download.svg);
  }
  

File path to SVG icon in acmecorp icons folder

This worked seamlessly in normal mode. However, in the Experience Editor and Pages, these icons didn’t display because Sitecore attempted to fetch the SVGs from the Content Management (CM) server rather than the rendering host. To resolve this, we needed to dynamically reference the correct hostname, ensuring that icons would load properly for both the public website and inside Sitecore editors.

Solution: Leveraging XM Cloud’s Sass Plugin

To do this, we had to modify the src\lib\next-config\plugins\sass.js file that comes with the Sitecore XM Cloud starter kit. This file is called by next.config.js, and by default it just sets path aliases for importing Sass files. We extended the sassOptions object of that file to include an additionalData property, where we could create the Sass variable that would hold our hostname. Then to get the hostname, we called Sitecore JSS's getPublicUrl() function to fetch the domain for whichever environment the code was running on (defaulted to an empty string if it couldn’t be found).

const path = require('path');
  const SassAlias = require('sass-alias');
  // BEGIN CUSTOMIZATION - make the public URL available to the sass files
  const getPublicUrl = require('@sitecore-jss/sitecore-jss-nextjs/utils').getPublicUrl;
  const publicUrl = getPublicUrl() || '';
  // END CUSTOMIZATION
  
/* * @param {import('next').NextConfig} nextConfig */ const sassPlugin = (nextConfig = {}) => { return Object.assign({}, nextConfig, { sassOptions: { importer: new SassAlias({ '@sass': path.join(__dirname, '../../../assets', 'sass'), '@fontawesome': path.join(__dirname, '../../../../node_modules', 'font-awesome'), }).getImporter(), // BEGIN CUSTOMIZATION - make the public URL available to the sass files additionalData: `$base-url: "${publicUrl}";`, // END CUSTOMIZATION }, }); };
module.exports = sassPlugin;

Then we were able to use $base-url in the SVG paths. The icons now appeared in edit mode.

&.icon-download {
      content: url(#{$base-url}/img/acmecorp/icons/icon_download.svg);
  }
  

Configuring Sass Variables for Storybook

However, our changes caused our Storybook build to fail with the following error:

ERROR in ./src/assets/main.scss (./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[15].use[1]!./node_modules/postcss-loader/dist/cjs.js!./node_modules/resolve-url-loader/index.js!./node_modules/@storybook/nextjs/node_modules/sass-loader/dist/cjs.js??ruleSet[1].rules[15].use[4]!./src/assets/main.scss)
  Module build failed (from ./node_modules/@storybook/nextjs/node_modules/sass-loader/dist/cjs.js):
  SassError: Undefined variable.
     ╷
  12content: url(#{$base-url}/img/acmecorp/icons/icon_download.svg);
     │                          ^^^^^^^^^
     ╵
    src\assets\links.scss 12:26  @import
    src\assets\main.scss 30:11                   root stylesheet
  

Storybook doesn’t need next.config.js to run, so it won’t have access to this variable we created in our src\lib\next-config\plugins\sass.js plugin. We’ll need to configure that variable another way for Storybook.

In our Storybook’s .storybook\main.ts file, we were already using the @storybook/addon-styling addon, and found that it has further options to configure it. We used the scssBuildRule object to tell Storybook how to process the Sass files.

const config: StorybookConfig = {
    staticDirs: ['../public'],
    addons: [
      // Other addons
      {
        name: '@storybook/addon-styling',
        options: {
          scssBuildRule: {
            test: /\.s[ac]ss$/,
            use: [
              'style-loader',
              {
                loader: 'css-loader',
                options: {
                  importLoaders: 3,
                  url: {
                    filter: (url) => {
                      if (url.startsWith("/img/acmecorp/icons/")) {
                        return false;
                      }
                      return true;
                    },
                  },
                },
              },
              'postcss-loader',
              'resolve-url-loader',
              {
                loader: 'sass-loader',
                options: {
                  additionalData: '$base-url: "";'
                }
              }
            ]
          },
  
// Other options }, }, ],
// Rest of the config };

This configuration contains many parts:

  • It sets up a rule for processing .scss and .sass files (Sass stylesheets).
  • The scssBuildRule uses a series of loaders to process these files:
    • style-loader: Injects the CSS into the DOM at runtime.
    • css-loader
      • Interprets @import statements, url(), and require() and resolves them.
      • It's configured to handle 3 loaders after it (importLoaders: 3).
      • It has a custom URL filter that excludes relative URLs for icons like "/img/acmecorp/icons/". This filter is used to prevent icon paths from being resolved because they do not exist at that location in the Storybook application.
    • postcss-loader: processes CSS with PostCSS.
    • resolve-url-loader: resolves relative paths in url() statements based on the original source file.
    • sass-loader: compiles Sass/SCSS files to CSS.
      • It adds a global Sass variable $base-url: ""; to all processed files so our icon URLs stay relative without a hostname in Storybook. The icon files are located in the ../public folder configured in staticDirs.

In the process of migrating from Sitecore XP to Sitecore XM Cloud, even seemingly minor details like handling icon paths in Sass files can present unexpected challenges. By leveraging Sitecore JSS's getPublicUrl() function, extending the sass.js configuration and changing how Storybook processes Sass files, we were able to ensure that the icons loaded correctly for Experience Editor and Pages.

Kelly Parks

Kelly Parks

Front-End Developer

Kelly is a Front-end Developer at Fishtank. She enjoys digging into details and working with clients to create solutions. In her free time, she enjoys learning about and growing plants, collecting rocks and minerals, and playing Dungeons & Dragons with her friends.