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);
}
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.
╷
12 │ content: 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()
, andrequire()
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.
- Interprets
postcss-loader
: processes CSS with PostCSS.resolve-url-loader
: resolves relative paths inurl()
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 instaticDirs
.
- It adds a global Sass variable
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.