Upgrade to Ralph UI v22.0.0
Shortly after the release of v21.0.0, a memory leak was discovered. We strongly recommend that you skip that version and upgrade straight to v22.0.0 instead, where this leak has been fixed. This guide will cover upgrading from v20.x.x straight to v22.0.0.
Why you should upgrade
Ralph Storefront now supports Node v16.x.x (since v21.0.0) and this version (v22.0.0) has a lot of performance improvements and bug fixes. For example, no more Apollo smart queries are used for fetching anything from the api.
Also, a lot of bloating has been removed from the package and the name of the package has been changed to @geins/ralph-ui
and is now available on npm: @geins/ralph-ui.
See release notes for more information
This is a HUGE update with a lot of required changes. Make sure to set aside enough time to go through the changes and test your storefront thoroughly after the upgrade.
Pre-requisites
You will need to install and use Node v16.x.x, we recommend running it on Node v16.20.0
Before upgrading your to Ralph UI v22.0.0 in your storefront, make sure you have already upgraded to v20.x.x. See guide here
Options for upgrading
Since v21.0.0 of Ralph UI now runs on Node v16.x.x, you will need to update your storefront to Ralph Storefront v2.2.0, to ensure compatibility.
There are two ways to upgrade to Ralph UI v22.0.0, either merge the changes from the Ralph Storefront repository into your project, or manually upgrade your project. Normally it would be recommended to merge the changes, but since this update also includes new linting, it might be easier to manually upgrade your project.
Here follows a guide for both options.
1. Update to Ralph Storefront v2.2.0 through merging
This way of updating ensures you get all important updates. It can sometimes be a bit time consuming dealing with conflicts, but this is usually how we do it.
Start by setting up Ralph Storefront as a remote in git and then merge the changes from the Ralph Storefront repository into your project.
Merge from remote
Firstly, make sure you have a GitHub personal access token. You can create one here.
Run the following commands in your Ralph Storefront project:
git remote add ralph https://${yourPersonalToken}@github.com/geins-io/ralph-storefront.git
git fetch ralph
git merge ralph/master
Dealing with merge conflicts
You will surely encounter merge conflicts when merging the changes from the Ralph Storefront repository into your project. Methodically go through each file with conflicts and resolve them.
Also, look through the auto merged ones. A lot of components and files have been removed (and in many cases moved to Ralph UI), so you will need to make sure that you are not removing anything that you are using or that you made custom changes to in your project.
When you are done, you can commit the changes and push them to your repository.
2. Manual upgrade
If the merge conflicts are too many or too complex, you can manually upgrade your project to Ralph Storefront v2.2.0. This section will try to guide you through the process.
Update dependencies
In package.json
, the list of dependencies should be:
"dependencies": {
"@apollo/client": "^3.8.1",
"@geins/ralph-ui": "^22.0.0",
"@nuxtjs/apollo": "4.0.1-rc.5",
"@nuxtjs/i18n": "^7.3.1",
"@nuxtjs/pwa": "^3.3.5",
"apollo-link": "^1.2.14",
"cookie": "^0.5.0",
"cookie-universal-nuxt": "^2.2.2",
"focus-visible": "^5.2.0",
"graphql": "^16.8.0",
"nuxt": "^2.17.2",
"nuxt-polyfill": "^1.0.3",
"nuxt-user-agent": "^1.2.2",
"vue-slide-up-down": "^2.0.1"
},
"devDependencies": {
"@babel/core": "^7.22.10",
"@babel/eslint-parser": "^7.22.10",
"@babel/runtime-corejs3": "^7.22.10",
"@nuxtjs/eslint-config": "^10.0.0",
"@nuxtjs/eslint-module": "^3.1.0",
"@nuxtjs/router": "^1.7.0",
"@nuxtjs/style-resources": "^1.2.1",
"@nuxtjs/stylelint-module": "^4.2.0",
"@vuese/cli": "^2.14.3",
"concurrently": "^8.2.1",
"core-js": "^3.32.0",
"cross-fetch": "^4.0.0",
"csv-parser": "^3.0.0",
"eslint": "^8.48.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-nuxt": "^3.1.0",
"eslint-plugin-prettier": "^5.0.0",
"file-save": "^0.2.0",
"husky": "^8.0.3",
"lint-staged": "^14.0.0",
"postcss": "^8.1.9",
"postcss-html": "^1.5.0",
"postcss-import": "12.0.1",
"prettier": "^3.0.2",
"prompts": "^2.4.2",
"sass": "^1.49.9",
"sass-loader": "^10.4.1",
"stylelint": "^15.10.0",
"stylelint-config-standard": "^34.0.0",
"stylelint-config-standard-scss": "^10.0.0",
"stylelint-config-standard-vue": "^1.0.0",
"svg-inline-loader": "^0.8.2",
"uppercamelcase": "^3.0.0",
"zen-observable-ts": "^0.8.21"
},
Add and remove dependencies accordning to your needs. A lot of the dependencies in the list above are added with specific versions, even if they are just used by other dependencies. This is to ensure that the versions are compatible with each other and with Node v16.x.x. If you need to add or remove any dependencies, you can use the list above as referenece if something breaks.
Before updating your dependencies, make sure you ar running on Node v16.x.x. To make sure to start from a clean slate, remove your node_modules
folder and your package-lock.json
file. Then clean your cache with npm cache clean --force
and then run npm install
to install the new dependencies.
Changing names
Some functions/variables/other has changed names.
- In
pages/product/_alias.vue
, change the name ofsizeChangeHandler
toskuChangeHandler
- In
pages/list/*.vue
, change the name of the middlewarelist-page-routing
toralph-list-page-routing
- Search your project for
@ralph/ralph-ui
and replace it with@geins/ralph-ui
- In the middleware folder, change the names of all three files to have
ralph-
in front of them, like so:ralph-authenticated.js
,ralph-list-page-routing.js
andralph-default.js
, also change the name of the import in these files to the same as the file name. - In
CaCheckout
, changeCaUdc ref="udc"
toCaNshift ref="nshift"
and changeCaCheckoutCarismar ... ref="checkoutCarismar"
toCaCheckoutInvoice ...ref="checkoutInvoice"
- Search your project for
eventbus
and replace it withthis.$ralphBus
. Also remove all imports ofeventbus
in components.
Replace functionality with mixins
In some cases, it's recommended to replace all functionality with mixins, to be able to receive new updates from Ralph UI.
- In
pages/content/_preview.vue
, replace all functionality withMixWidgetPreview
- In
pages/content/_alias.vue
, replace all functionality withMixContentPage
- In
pages/account/settings.vue
, replace all functionality withMixAccountSettings
- In
pages/account/reset-password.vue
, replace all functionality withMixResetPassword
- In
pages/account/order.vue
, replace all functionality withMixAccountOrders
- In
pages/account/index.vue
, replace all functionality withMixAccountIndex
- In
pages/account/balance.vue
, replace all functionality withMixAccountBalance
Language keys
- Change
PICK_SIZE
toPICK_SKU
in your language files - Add
PICK_VARIANT
to your language files (if you want to use it) - Add
FEEDBACK_ORGANIZATION_ID_NOT_VALID
if you're usingCaCheckoutInvoice
Add files
Add a folder named config
to your root and these files to it:
export const routePaths = {
category: '/c',
brand: '/b',
product: '/p',
search: '/s',
discountCampaign: '/dc',
list: '/l',
};
export function getMarketSettings() {
// Setting the domain settings and market settings based on if env-variable DOMAINS exists
// Default settings for multi market / multi language
let domainSettings = {
differentDomains: false,
strategy: 'prefix',
};
let domainUrls = null;
// Default settings for market for publicRuntimeConfig
let marketSettings = {
isMultiLanguage: true,
marketInPath: true,
};
if (process.env.DOMAINS) {
const domains = process.env.DOMAINS.split(',');
domainUrls = domains
?.map((domain) => {
const domainArr = domain?.split('|');
return {
[domainArr[0]]: domainArr[1] || '',
};
})
.reduce((result, item) => {
const key = Object.keys(item)[0];
result[key] = item[key];
return result;
}, {});
// If using DOMAINS, turn off multilang and marketInPath
marketSettings = {
isMultiLanguage: false,
marketInPath: false,
};
// If site should have only language prefix and no market prefix, remove the following declaration
domainSettings = {
differentDomains: false,
strategy: 'prefix_except_default',
};
if (domains.length > 1) {
// If more than one domain, set diffrentDomains to true
domainSettings = {
differentDomains: true,
strategy: 'no_prefix',
};
}
}
return {
domainSettings,
domainUrls,
marketSettings,
};
}
import fs from 'fs';
import csv from 'csv-parser';
const imageSizesFile = './config/ImageSize.csv';
export async function getImageSizes() {
const imageSizesStream = fs.createReadStream(imageSizesFile);
const imageSizeObject = {};
const imageSizesStreamRead = new Promise((resolve) => {
imageSizesStream
.pipe(csv())
.on('data', (row) => {
// Get the value from the PartitionKey (Image type) Column and make it lowercase (Because of input inconsistencies in the source document)
const PartitionKey = row.PartitionKey.toLowerCase();
// Create the data for this row
const imageRow = {
folder: row.Folder,
descriptor: row.Width + 'w',
};
// Check if the imagesizeobject has the current image tyoe already, if so add to it, otherwise create it
if (imageSizeObject[PartitionKey]) {
imageSizeObject[PartitionKey].push(imageRow);
} else {
imageSizeObject[PartitionKey] = [imageRow];
}
})
.on('end', () => {
resolve(imageSizeObject);
});
});
return await imageSizesStreamRead;
}
import { gql } from 'graphql-tag';
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client/core';
import fetch from 'cross-fetch';
const apolloClient = new ApolloClient({
cache: new InMemoryCache({}),
link: new HttpLink({ uri: process.env.API_ENDPOINT, fetch }),
});
const defaultMetaQuery = gql`
query listPageInfo(
$channelId: String
$languageId: String
$marketId: String
) {
listPageInfo(
channelId: $channelId
languageId: $languageId
marketId: $marketId
) {
meta {
description
title
}
}
}
`;
const getMarketsQuery = gql`
query channel($channelId: String) {
channel(channelId: $channelId) {
defaultMarketId
markets {
id
defaultLanguageId
alias
virtual
onlyDisplayInCheckout
groupKey
allowedLanguages {
id
name
code
}
country {
name
code
}
currency {
name
code
}
}
}
}
`;
const fallbackChannelId = process.env.FALLBACK_CHANNEL_ID;
const fallbackMarketAlias = process.env.FALLBACK_MARKET_ALIAS;
const defaultLocale = process.env.DEFAULT_LOCALE;
export async function getFallbackMeta() {
const getMetaQuery = await apolloClient.query({
query: defaultMetaQuery,
variables: {
channelId: fallbackChannelId,
marketId: fallbackMarketAlias,
languageId: defaultLocale,
},
context: {
headers: {
'X-ApiKey': process.env.API_KEY,
},
},
});
return await getMetaQuery.data.listPageInfo.meta;
}
export async function getFallbackMarkets() {
const getMarkets = await apolloClient.query({
query: getMarketsQuery,
variables: {
channelId: fallbackChannelId,
},
context: {
headers: {
'X-ApiKey': process.env.API_KEY,
},
},
});
return await getMarkets.data.channel.markets;
}
export const channelSettings = [
{
channelId: '1|se',
siteName: 'Ralph Storefront', // Change this name to your sites name
// All theme settings will be converted into css variables and added to the root html element
theme: {
'accent-color': '#363636', // Change to your accent color
// Add more variables here if you want to get them converted into css variables
},
},
];
export const currentChannelSettings =
channelSettings.find(
(i) => i.channelId === process.env.FALLBACK_CHANNEL_ID,
) || channelSettings[0];
Also, add the config
folder to your Dockerfile
file like this (last line):
...
ARG NODE_ENV
ENV NODE_ENV=${NODE_ENV}
RUN npm run build \
&& node-prune \
&& mkdir output \
&& cp -r package.json nuxt.config.js node_modules .nuxt static scripts config output
...
Update your nuxt.config.js
file
A lot of bloating of nuxt.config.js
has been moved to external config files. Remove all code above the export default async() => {
and replace it with this:
import path from 'path';
import { getImageSizes } from './config/image-sizes';
import { getFallbackMarkets, getFallbackMeta } from './config/fallback-data';
import { getMarketSettings } from './config/market-settings';
import {
channelSettings,
currentChannelSettings,
} from './config/channel-settings';
import { routePaths } from './config/route-paths';
import DirectoryNamedWebpackPlugin from './config/directory-named-webpack-resolve';
export default async() => {
...
Then after the export default async() => {
add this:
...
export default async() => {
const ralphEnv = process.env.RALPH_ENV || 'prod';
const imageSizes = await getImageSizes();
const fallbackMarkets = await getFallbackMarkets();
const fallbackMeta = await getFallbackMeta();
const { domainSettings, domainUrls, marketSettings } = getMarketSettings();
return {
...
};
};
Then change the rest of the file to work with this setup. For exampel defaultMeta
has changed namne to fallbackMeta
.
Plugins
The plugins-section should be:
...
plugins: [
{
src: '~/node_modules/@geins/ralph-ui/plugins/ralph.js',
},
{
src: '~/node_modules/@geins/ralph-ui/plugins/set-css-variables.js',
mode: 'client',
},
{
src: '~/node_modules/@geins/ralph-ui/plugins/broadcast-channel.js',
mode: 'client',
},
{
src: '~/node_modules/@geins/ralph-ui/plugins/headers-control.js',
mode: 'server',
},
],
...
Modules
To the modules
and buildModules
, add:
...
modules: [
...
// Doc: https://github.com/nuxt-community/style-resources-module
'@nuxtjs/style-resources',
// Doc: https://www.npmjs.com/package/nuxt-polyfill
'nuxt-polyfill',
...
],
...
buildModules: [
...
[
// Internal build module
'~/node_modules/@geins/ralph-ui/modules/ralph-build',
...
],
...
],
...
Config
Change router middleware from default
to ralph-default
.
...
router: {
middleware: ['ralph-default'],
},
...
Add/replace the following variables in the publicRuntimeConfig
:
...
publicRuntimeConfig: {
...
fallbackChannelId: process.env.FALLBACK_CHANNEL_ID,
fallbackMarketAlias: process.env.FALLBACK_MARKET_ALIAS,
currentChannelSettings,
channelSettings,
fallbackMarkets,
imageSizes,
...marketSettings,
...
},
...
Also add statesToPersist
and choose what modulles/states in your store that you want to be persisted. The list below is the default list, but you can add or remove states as you like, although its strongly recommended to keep the list/relocateAlias
and list/relocatePage
states.
...
publicRuntimeConfig: {
...
statesToPersist: [
'favorites',
'customerType',
'vatIncluded',
'list/relocateAlias',
'list/relocatePage',
],
...
},
...
We also introduced a variable called ralphLog
from which you can set what to log to the console in development mode, Add this to your publicRuntimeConfig
:
...
publicRuntimeConfig: {
...
ralphLog: {
all: false,
api: false,
events: false,
checkout: false,
warnings: false,
},
...
},
...
Config files
Remove stylelint.config.js
and add .stylelintrc
with the following content (or the content you prefer):
{
"extends": ["stylelint-config-standard"],
"overrides": [
{
"files": ["*.scss", "**/*.scss"],
"extends": ["stylelint-config-standard-scss"]
},
{
"files": ["*.vue", "**/*.vue"],
"extends": [
"stylelint-config-standard-scss",
"stylelint-config-standard-vue/scss"
]
}
],
"rules": {
"scss/operator-no-newline-after": null,
"no-empty-source": null,
"rule-empty-line-before": [
"always-multi-line",
{ "ignore": ["after-comment", "first-nested"] }
]
}
}
Add a folder named .husky
to your root and add a file named pre-commit
with the following content:
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged
Add a file called .npmrc
to your root with the following content:
install-links=true
legacy-peer-deps=true
In package.json
, add/replace the following and remove the husky
section.
...
"scripts": {
...
"lint": "npm run lint:js && npm run lint:scss",
"lint:js": "eslint --fix --ext .js,.vue --ignore-path .gitignore .",
"lint:scss": "npx stylelint \"**/*.{css,scss,vue}\" --fix",
...
"ralph-create": "node ./scripts/ralph-create.cjs",
"ralph-ui-docs": "npm --prefix ./node_modules/@geins/ralph-ui/ run documentation-serve"
},
"lint-staged": {
"*.{js,vue}": "npm run lint:js",
"*.{css,scss,vue}": "npm run lint:scss"
},
...
Style changes
Since we are now running postcss isntead of node-sass, you will need to make a few changes to your style files. Also, we have decided to change the names of for example mixins to kebab-case instead of camelCase.
Change yout styles/helpers/_sizing.scss
file to:
@use 'sass:math';
$rem-base: 16px;
@function strip-unit($num) {
@return math.div($num, ($num * 0) + 1);
}
@function rem-calc($value, $base-value: $rem-base) {
$value: math.div(strip-unit($value), strip-unit($base-value)) * 1rem;
@if $value == 0 {
$value: 0;
}
// Turn 0rem into 0
@return $value;
}
Change your styles/helpers/_layout.scss
file to:
@mixin product-image-overlay {
&::after {
content: '';
display: block;
width: 100%;
height: 100%;
background: radial-gradient(
farthest-corner at center,
transparent 27%,
rgb(9 9 9 / 0%) 31%,
rgb(170 170 170 / 10%) 100%
);
position: absolute;
top: 0;
left: 0;
z-index: 1;
pointer-events: none;
}
}
@mixin icon-circle {
@include flex-calign;
background-color: $c-white;
width: 36px;
height: 36px;
transition: all 200ms ease;
border-radius: 50%;
box-shadow: 0 0 6px rgb(0 0 0 / 10%);
border: $border-light;
font-size: $font-size-m;
&:focus-visible {
box-shadow: 0 0 0 $focus-border;
}
}
@mixin scrollbar-style($color: $c-medium-gray, $hover: $c-darkest-gray) {
scrollbar-color: $color transparent;
scrollbar-width: thin;
&::-webkit-scrollbar {
background-color: transparent;
width: $px16 * 0.35;
height: $px16 * 0.35;
}
&::-webkit-scrollbar-track {
border-radius: 100vw;
}
&::-webkit-scrollbar-thumb {
background-color: $color;
border-radius: 100vw;
}
&::-webkit-scrollbar-thumb:hover {
background-color: $hover;
}
}
@mixin hide-scrollbar {
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
}
}
In styles/variables/_colors.scss
, add @use 'sass:color';
at the top of the file and change the file to use the new syntax. For exampel, instead of lighten
you can use color.adjust
.
Other changes
- Move the file
static/ImageSize.csv
to theconfig
folder - In
CaProductCard
, change allNuxtLink
toCaClickable
and theclick.native
event handler toclicked
- In
Dockerfile
, changenode:14.15.5-alpine
tonode:16.20.0-alpine
- In
layouts/error.vue
, change so that theloading/end
is called onmounted
instead ofcreated
- In
CaListPage
change$apollo.queries.products.loading
passed toCaListPagination
toprevPageLoading
andnextPageLoading
- To fix an error with both internal and external confirm page showing, make this change to your
pages/checkout/confirm.vue
file:
<template>
...
<CaCheckoutHeader :title="$t('ORDER_CONFIRM_TITLE')" />
<CaCheckoutSection :loading="loading" :bottom-arrow="false">
<CaCheckoutExternal
v-if="$route.query.kid || $route.query.sid || $route.query.wid"
:type="type"
:data="confirmSnippet"
/>
<div v-else class="ca-checkout-confirm">
...
</template>
...
- In
pages/content/_alias.vue
, change so thatCaWidgetArea
useswidgetData
to use the data fetched on the server side inMixContentPage
, like so:
<template>
...
<CaWidgetArea :widget-data="widgetData" :alias="$route.params.alias" />
...
</template>
...
- In
pages/product/_alias.vue
, make sure to only use one mixin:MixProductPage
and remove the others, like so:
...
export default {
...
mixins: [MixProductPage],
...
};
...