📑 Manual Migration
This manual migration process should be run after the automated migration process, to complete the missing parts, or debug issues in the migration CLI output.
Transitioning to ESM
Starting 3.0, in a effort to modernize the codebase, we transitioned to ESM. This migration allows a better alignment with browsers standards, an improved static analysis, enhanced language features and overall a better consistency in the JavaScript ecosystem. To learn more about it, see:
Updates in your codebase
Updating exports
To cope with this transition, you should check that your code does not use
module.exports =
or module.exports.someNamedExport =
syntax, and replace it
with export default
or export {}
otherwise.
On linux-based systems, you can use the grep
command on your project to find
those occurences:
grep "module.exports" src/ -r
Uses of __dirname
__dirname
isn't available anymore in ESM. To cope with this change, you will
need to replace its usage. For example:
import { dirname } from "path";
import { fileURLToPath } from "url";
const __dirname = dirname(fileURLToPath(import.meta.url));
For more information about alternatives to __dirname
, see:
- https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/.
- https://dev.to/brcontainer/alternative-for-dirname-in-node-when-using-ecmascript-modules-5gni
Updates in your dependencies
You may also have issues with some of your project dependencies that are not ESM-compatible.
First of all, we recommend that you check if the dependencies you are using have a newer version that is ESM-compatible. If so, you can update them to the latest version. If it is not the case, we encourage you to reconsider their usage in your project, and remove them if possible or replace them with an ESM-compatible alternative. It also is a sign of a library that is not actively maintained. You can also contribute to the library by opening an issue or a pull request to make it ESM-compatible, of course!
As a last resort, you can adapt your Vite configuration (vite.config.ts
) to
handle those cases by tweaking the optimization settings. See the
Vite documentation for more information. More
particularly these pages:
- https://vitejs.dev/guide/dep-pre-bundling.html
- https://vitejs.dev/guide/ssr.html#ssr-externals
- https://vitejs.dev/guide/troubleshooting.html
- https://remix.run/docs/en/main/future/vite#esm--cjs
Components
SEO Components
The xxxSeo
components (eg: HomeSeo
) have been removed in favor of
Remix Meta.
If you use SEO components or have overriden Seo components, you need to:
- Remove import and usage of the component from your page
- Use the meta function with our generateMetas helper to inject your custom metadatas in your Route Module.
Refactor pages to Remix routes
Page Enhancer
HOC usually were responsible for data loading in 2.x
. As we
now embrace
Remix Server/Client Model,
page enhancers must be removed in favor of
Remix route loaders.
You can see an example in the
category route file
on how the data loading is now used with the loader
method.
You need to pay attention to two thing if you've overriden this file :
- Did you update the query to fetch category?
We now recommend you to update the GraphQL query used in category route and pass down any new props to the Category component.
- Did you added more HOC to the
Enhancer
?
You can either add your custom HOC to the Category
component or replace HOC
with hooks if relevant.
Product
page
In v3, the base theme got completely rid of any HOC, by leveraging on standard mechanics from Remix loaders and hooks:
withTrackPage
andwithProductTracking
responsibilities have been transfered touseTrackPage
anduseProductPage
hooks- Loading state management is done directly at route loader level
Account
pages
Similarly, page-base HOCs on the Account page have been completely removed, and replaced with standard Remix and React mechanics. Here's a list of all removed Enhancers in those pages:
EnhanceDownloadableProduct
(see merge request)EnhanceAddressBook
(see merge request)EnhanceDashboard
(see merge request)
withTrackOnMount
HOC depreciation
withTrackOnMount
has been deprecated in favor of a new hook useTrackOnMount
.
The new hook has a slightly different signature:
Old prop | New prop | Prop type |
---|---|---|
isResolvedFromProps | hasResolved | function boolean |
mapPropsToProperties | createProperties | function |
shouldMountEvent
, shouldUpdateEvent
and trackEvent
have been left
unchanged.
// Before (HOC)
withTrackOnMount({
event: "Product Viewed",
isResolvedFromProps: (props) => !props.loading && props.product,
mapPropsToProperties: (props) => {
const finalPrice = props.product?.prices?.finalPrice?.priceInclTax?.value;
return {
sku: props.product.sku,
name: props.product.name,
price: finalPrice?.amount,
currency: finalPrice?.currency,
};
},
});
// After (Hook)
useTrackOnMount({
event: "Product Viewed",
hasResolved: !props.loading && Boolean(props.product),
createProperties: () => {
const finalPrice = props.product?.prices?.finalPrice?.priceInclTax?.value;
return {
sku: props.product?.sku,
name: props.product?.name,
price: finalPrice?.amount,
currency: finalPrice?.currency,
};
},
});
withProductTracking
replaced with useProductTracking
Similarly, the page-based HOC withProductTracking
has been completely removed,
in favor of a new useProductTracking
hook.
If you used this HOC in your code, you will have to replace it by calling
useProductTracking
in the component the HOC was previously used on.
useProductTracking
only takes the product as parameter.
withProductAddedPopIn
removed
The HOC withProductAddedPopIn
has been completely removed, in favor of using a
local useState
insied the Product
component.
If you used this HOC in your code, you will have to replace it by using
useState
in the component the HOC was previously used on:
import { useState } from "react";
// ...
const MyComponent = () => {
const [productAdded, setProductAdded] = useState(null);
// ...
return (
<div>
<ProductAddedPopIn product={productAdded} hidePopin={() => setProductAdded(null)} />
<ProductView
onProductAdded={(product) => setProductAdded(product)}
{/* ... */}
/>
</div>
);
}
withCartTracking
replaced with useCartTracking
The page-based HOC withCartTracking
has been completely removed, in favor of a
new useCartTracking
hook.
If you used this HOC in your code, you will have to replace it by calling
useCartTracking
in the component the HOC was previously used on.
useCartTracking
only takes the cart as parameter.
withLogoutMutation
has been removed
The page-based HOC withLogoutMutation
has been completely removed, and has
been replaced with a /logout
route.
If you used this HOC in your code, you can remove it and use a link to /logout
instead. See skeleton/app/routes/logout.tsx
and
theme/pages/Account/Logout/Logout.jsx
for examples.
makeCommandDispatcherWithCommandFeedback
API change
makeCommandDispatcherWithCommandFeedback
API has changed.
In version 2, the second parameter of makeCommandDispatcherWithCommandFeedback
was an optional boolean (default true
) and if this parameter was evaluated to
a truthy value, the resulting component had the responsibility of displaying the
result of the command with the InlineAlert
component. In version 3,
makeCommandDispatcherWithCommandFeedback
now expects an optional React
component to display the result of the command.
To cope with this change, you need to review all components using
makeCommandDispatcherWithCommandFeedback
:
- if
makeCommandDispatcherWithCommandFeedback
is called with a falsy second parameter, you don't have to change anything - if
makeCommandDispatcherWithCommandFeedback
is called with a truthy second parameter or without the second parameter, you have to adapt your code. To have exactly the same behavior as before, the new call should look like:makeCommandDispatcherWithCommandFeedback(
{
/* commands definition */
},
({ success, message }) => {
return (
<InlineAlert appearance={success ? "success" : "error"}>
{message}
</InlineAlert>
);
}
);
withEntityNotFound
API change
withEntityNotFound
API has changed.
In version 2, the parameter NotFoundComponent
was optional (defaulted to
NotFound
component). This HOC is now deprecated and is now in the
theme-agnostic @front-commerce/compat
package. If you still want to use
withEntityNotFound
, you must now provide a NotFoundComponent
component
parameter.
To cope with this change, you need to review all component using
withEntityNotFound
to make sure a NotFoundComponent
parameter is passed to
it. For example, by using the component it was previously defaulted to:
import NotFound "theme/modules/PageError/NotFound/NotFound"
/*
... your component
*/
export default withEntityNotFound({
isFound: () => false,
NotFoundComponent: NotFound,
})(MyComponent);
A even better way would be to get rid of withEntityNotFound
, and instead
handle this mechanism directly withing the related loader
by throwing a 404
response there. For more information about this practice, see
Remix's guide on handling 404s.
PartialProductQuery
removal
In version 3, PartialProductQuery
has been completely removed as it is not
used by the base theme anymore. If it was used in your project, you will have to
replace it with your own query or fragment.
For reference, the query was:
#import "theme/modules/ProductView/ProductItem/ProductItemFragment.gql"
query PartialProductQuery($sku: String!) {
product(sku: $sku) {
...ProductItemFragment
}
}
Sass Migration
In the latest version of Front-Commerce we converted all global Sass variables to CSS variables during the automated migration. This allows for a more modular approach to theming and a better developer experience. However, there are still some manual steps that need to be taken to complete the migration.
Breakpoints Removal
The following scss helpers have been removed. If you were using them, you have to manually add them back to your project or update implementation
grid-breakpoints
variablebreakpoint-min
functionmedia-breakpoint-up
mixin
To replace usage of these scss methods
Instead of relying on the removed Sass mixins, we recommend to now use a standardized approach that involves directly using media queries in your CSS code. This approach provides better control, readability, and maintenance of responsive styles.
Here are the breakpoints values we used:
xs
: 0sm
: 576pxmd
: 768pxlg
: 992pxxl
: 1200px
For each breakpoint where you were previously using the breakpoint-min
function, replace it with a media query using the min-width
property. For
example:
Before:
$min: breakpoint-min("sm");
@media (min-width: $min) {
// Styles for 'sm' and wider
}
After:
@media (min-width: 576px) {
// Styles for 'sm' and wider
}
For cases where you were using the media-breakpoint-up
, you can use a media
query with min-width
too. For example:
Before:
@include media-breakpoint-up("md") {
// Styles for 'md' and wider
}
After:
@media (min-width: 768px) {
// Styles for 'sm' and wider
}
And you can remove @include media-breakpoint-up("xs") {
as it applied CSS
rules systematically.
To restore the usage of these scss methods
You can copy it from the v2.x code and add it to a breakpoints override or a custom file, then you should import it in any of your scss files which require either the variable, mixin or function, for example:
@import "theme/components/atoms/Breakpoints/breakpoints";
.container {
background-color: red;
@media screen and (min-width: $menuBreakpoint) {
max-width: 40em;
}
@include media-breakpoint-up(sm) {
background-color: blue;
}
@include media-breakpoint-up(md) {
background-color: green;
}
@include media-breakpoint-up(lg) {
background-color: yellow;
}
}
After running the codemods on your project, you can find these deprecations by
searching TODO (Codemod generated)
in your project.
Media queries
If you use any media queries which use global sass variables, you need to manually import the file defining the variables.
If you have the following example component, You will need to import the
breakpoints.scss
file.
@import "theme/components/atoms/Breakpoints/breakpoints";
fieldset {
border: none;
font-size: var(--regularSize);
.fieldset--large {
max-width: 30em;
@media screen and (min-width: $menuBreakpoint) {
max-width: 40em;
}
}
}
If you're using the $boxSizeMargin
, $smallContainerWidth
, or
$containerWidth
variables you should to import @import "theme/variables";
instead.
After running the codemods on your project, you can easily find these
occurrences by searching TODO (Codemod generated)
in your project.
Color functions
If you use any Sass color functions you will need to directly import the color.scss file at the location where it is used. This is because Sass can not process the functions on CSS variables.
If you have the following example component:
.Example {
color: darken(var(--colorPrimary), 10%);
}
You would need to import the color.scss
directly in the Example.scss
file
@import "theme/components/atoms/Colors/colors";
.Example {
color: darken(var(--colorPrimary), 10%);
color: darken($colorPrimary, 10%);
}
Calculations
The automated migration step takes care of changing SASS global variables to
CSS custom properties
and of transforming declarations value with a unique multiplication and
the usage of math.div
so that it becomes a valid CSS declaration:
.example {
padding: $boxSizeMargin;
padding: var(--boxSizeMagin);
margin-top: $boxSizeMargin * 2;
margin-top: calc(var(--boxSizeMagin) * 2);
margin-bottom: math.div($boxSizeMargin, 4);
margin-bottom: calc(var(--boxSizeMargin) / 4);
}
However for more complex calculations or for declarations with several values,
only the usage of the SASS variable will be updated. After this step, a manual
fix is needed. When starting Front-Commerce, sass
is executed and it will
report any invalid expressions. Here is a list of common pattern to fix:
.example {
padding: var(--boxSizeMargin) * 2 var(--boxSizeMargin) * 4 var(
--boxSizeMargin
) * 2 0;
padding: calc(var(--boxSizeMargin) * 2) calc(var(--boxSizeMargin) * 4) calc(
var(--boxSizeMargin) * 2
) 0;
max-width: calc(#{var(--boxSizeMargin) * 24} + 50ch);
max-width: calc(var(--boxSizeMargin) * 24 + 50ch);
margin: calc(-var(--boxSizeMargin) / 4) * 3;
margin: calc(-var(--boxSizeMargin) * 3 / 4);
padding-left: 1.5rem - calc(var(--boxSizeMargin) / 2);
padding-left: calc(1.5rem - calc(var(--boxSizeMargin) / 2));
padding-right: 1.5rem - calc(var(--boxSizeMargin) / 2);
padding-right: calc(1.5rem - calc(var(--boxSizeMargin) / 2));
}
@extend
handling
If @extend
is used with a selector defined in another stylesheet, you need to
manually import that stylesheet. For example, sass
would fail compiling the
following stylesheet:
.example {
@extend .another-selector;
}
This is happening because .another-selector
is unknown, so the solution is to
import the stylesheet defining it:
@import "theme/some/path/defining/another-selector";
.example {
@extend .another-selector;
}
Modal Routes
In V2, a modal state parameter facilitated rendering arbitrary routes in a modal
during routing. In Remix, this is non-trivial unless the target route is a child
route. We have introduced a ModalLink
component to replace the previous
mechanism. It generates a link that displays a component in a modal upon user
click, with the modal state reflected in the URL hash for sharing and consistent
UI presentation across users.
Here is an example of the required changes:
import { ModalLink } from "theme/components/organisms/Modal";
import Login from "theme/components/organisms/Login";
export function Header() {
return (
<header>
<Link to={{ pathname: "/whatever", state: { modal: true } }}>Login</Link>
<ModalLink to="/login" linkContent="Login">
<Login />
</ModalLink>
</header>
);
}
Environment variables
Front-Commerce
In V3, the FRONT_COMMERCE_DISABLE_OFFLINE_MODE
environment variable has been
removed completely.
Renamed variables:
V2 | V3 |
---|---|
FRONT_COMMERCE_FACEBOOK_CLIENT_ID | FRONT_COMMERCE_LOGIN_FACEBOOK_CLIENT_ID |
FRONT_COMMERCE_FACEBOOK_CLIENT_SECRET | FRONT_COMMERCE_LOGIN_FACEBOOK_CLIENT_SECRET |
FRONT_COMMERCE_GOOGLE_CLIENT_ID | FRONT_COMMERCE_LOGIN_GOOGLE_CLIENT_ID |
FRONT_COMMERCE_GOOGLE_CLIENT_SECRET | FRONT_COMMERCE_LOGIN_GOOGLE_CLIENT_SECRET |
FRONT_COMMERCE_COOKIE_MAX_AGE_IN_MS | FRONT_COMMERCE_COOKIE_MAX_AGE_IN_SECONDS |
FRONT_COMMERCE_COOKIE_MAX_AGE_IN_SECONDS
is now a duration in seconds and not
in milliseconds as it was in v2.
Magento
In V2, Magento1 and Magento2 environment variables were prefixed the same way.
FRONT_COMMERCE_MAGENTO_*
, in V3, we have split them into
FRONT_COMMERCE_MAGENTO1_*
and FRONT_COMMERCE_MAGENTO2_*
.
- FRONT_COMMERCE_MAGENTO_CONSUMER_KEY=token
- FRONT_COMMERCE_MAGENTO_CONSUMER_SECRET=token
- FRONT_COMMERCE_MAGENTO_ACCESS_TOKEN=token
- FRONT_COMMERCE_MAGENTO_ACCESS_TOKEN_SECRET=token
## Magento 1
+ FRONT_COMMERCE_MAGENTO1_CONSUMER_KEY=token
+ FRONT_COMMERCE_MAGENTO1_CONSUMER_SECRET=token
+ FRONT_COMMERCE_MAGENTO1_ACCESS_TOKEN=token
+ FRONT_COMMERCE_MAGENTO1_ACCESS_TOKEN_SECRET=token
## Magento 2
+ FRONT_COMMERCE_MAGENTO2_CONSUMER_KEY=token
+ FRONT_COMMERCE_MAGENTO2_CONSUMER_SECRET=token
+ FRONT_COMMERCE_MAGENTO2_ACCESS_TOKEN=token
+ FRONT_COMMERCE_MAGENTO2_ACCESS_TOKEN_SECRET=token
Other optional variables updated:
2.x | 3.x - Magento1 | 3.x - Magento2 |
---|---|---|
FRONT_COMMERCE_MAGENTO_ADMIN_PATH | FRONT_COMMERCE_MAGENTO1_ADMIN_PATH | FRONT_COMMERCE_MAGENTO2_ADMIN_PATH |
FRONT_COMMERCE_MAGENTO_TIMEOUT | FRONT_COMMERCE_MAGENTO1_TIMEOUT | FRONT_COMMERCE_MAGENTO2_TIMEOUT |
FRONT_COMMERCE_MAGENTO_ADMIN_TIMEOUT | FRONT_COMMERCE_MAGENTO1_ADMIN_TIMEOUT | FRONT_COMMERCE_MAGENTO2_ADMIN_TIMEOUT |
FRONT_COMMERCE_XRAY_MAGENTO_VERSION | FRONT_COMMERCE_XRAY_MAGENTO1_VERSION |