📑 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 | ❌ N/A |
makeImageProxyRouter
→ createResizedImageResponse
The makeImageProxyRouter
function has been removed in favor of the new
createResizedImageResponse
function. You can read more about the
implementation details in the
createResizedImageResponse
documentation.
All images which are located under public/images/resized/*
should now be moved
to public/images/*
. This will allow for the
/images/resized/*
resource route
to be reserved for the new createResizedImageResponse
function.
Removal of SmartLayout
component
In v3 we removed the SmartLayout
component. This component was used to enhance
the Layout
component with navigationMenu for the header and footer.
The Layout
component now requires two props headerNavigationMenu
and
footerNavigationMenu
to be passed to it.
This is can be retrieved via a loader: see example in
_main.tsx
layout
export const loader = async ({ context }: LoaderFunctionArgs) => {
const app = new FrontCommerceApp(context.frontCommerce);
const headerResponse = await app.graphql.query(HeaderQueryDocument, {
name: "data",
});
const footerResponse = await app.graphql.query(NavigationMenuDocument, {
name: "categories",
});
return json({
headerNavigationMenu: headerResponse?.navigationMenu || [],
footerNavigationMenu: footerResponse?.navigationMenu || [],
});
};
react-helmet-async
removal
In version 3, react-helmet-async
has been removed as it does not make sense in
a remix application. If you used this in any custom components or pages, you
will now need to move it the
meta
function of the route.
Client side log handler removal
The client side log mechanism has been removed from Front-Commerce 3.0.
For future version, we plan to bring back this feature. In the meantime, if you
are using this feature or you have overridden a component using this feature,
you need to remove it. In version 2.x, the API was imported from
web/core/logs/logHandler
.
@loadable/component
removal
@loadable/component
is no longer a dependency of Front-Commerce. That means
components that were previously made loadable are now loaded synchronously as
any other component. In terms of usage, in most cases this change should be
transparent. That's not the case if
you are manually preloading a component with Component.preload()
or if you have overridden a file that is doing it. In those case, you have to
remove .preload()
calls.
In addition, you can still use @loadable/component
in your custom components
and then you have to make sure @loadable/component
is a dependency of your
project.
CartSeo
removal
In version 3, CartSeo
has been removed as it is not used by the base theme
anymore. If you overrode this component or one using it, you will need to move
any of the customisation to the cart
route
meta
function.
EmptyCart
refactor
The component EmptyCart
has been refactored and its HOC EnhanceEmptyCart
has
been removed to benefit from data served directly from the cart
route loader.
It is now rendered directly as a page in _main.cart.tsx
instead of rendered as
a component in Cart
. If you have overridden this component in you project, you
will need to ensure the changes are compatible with the new version.
See related MR (!2525) for more informations.
Removed MenuNavigation
component
The component MenuNavigation
was only used in the default theme of
Front-Commerce, and was subsequently removed in version 3.0 (see
related MR)
react-apollo
usage removed from theme-chocolatine
In an effort to remove react-apollo
dependency in theme chocolatine, we
removed all occurences of graphql()
, useQuery()
and useLazyQuery()
calls
in it, and replace them by either direct API calls using remix's useFetcher
,
or move them to the related route loader when possible. This means a good number
of Enhancers
have also been reworked, and sometimes removed completely. Here's
the list of the Enhancers
that have been removed:
EnhanceEmptyCart
(see MR):EmptyCart
component is now used as a page rendered directly under/cart
routeEnhanceCart
(see MR) was moved to the/cart
route loaderwithCountries
(see MR): replaced withuseCountries
hookEnhanceChoosePayment
(see MR): replaced withusePaymentMethods
hookEnhanceHeader
(see MR): replaced withuseCustomer
anduseCart
checkCart
(see MR): refactorwithCartId
(see MR): replaced withwithCart
EnhanceMiniCartContent
(see MR): replaced withuseCart
EnhanceCartRecap
(see MR): replaced withuseCart
withCheckoutSuccessTracking
(see MR): replaced withuseCheckoutSuccessTracking
GscText
's HOC (see MR): replaced withuseStore
EnhanceProductReviews
(see MR): replaced withuseProductReviews
withIsSubscribedToInStockAlert
(see MR): replaced withuseIsSubscribedToStockAlert
EnhanceSearchBar
(see MR): moved logic touseSearchBar
Additionally, some Enhancers
and HOCs were modified for the same purpose. If
you those were overriden in your project, you will have to check what change and
adapt it in your code:
EnhanceChooseShippingMethod
(see MR)EnhanceAddToCart
(see MR)EnhanceAddress
(see MR)withCheckoutTracking
(see MR)EnhanceProductGallery
(see MR)EnhancePublishProductreview
andEnhanceLoginForm
(see MR)EnhanceSubscribeToInStockAlert
(see MR)EnhanceCurrencySelector
(see MR)EnhanceAddToCartModal
(see MR)
useDevice()
removal
We removed the useDevice()
hook in version 3.0. It was unused in the base
theme, and we had no use cases in mind for it.
Device information are still available in the configuration. Access it using
config.device
. Please contact us if you have a use case we don't support
anymore.
Sitemap Changes
Please refer to the the Customize the Sitemap documentation for more details on how to implement dynamic sitemaps in v3.
Sitemap URL change
In v3, the sitemap URL has been changed from /sitemaps/sitemap.xml
to
/sitemap.xml
.
SitemapLoader
- The
registerNodesFetcher
method will no longer work in v3, instead you should register a fetchers via the sitemap service - The
overrideNodesFetcher
method will no longer work in v3, instead you should override a fetcher via the sitemap service
GraphQL Schema
- The
Sitemapable
GraphQL interface has been deprecated for removal. It can be removed from your project. - The
SitemapImage
GraphQL type has been deprecated for removal. It can be removed from your project.
Deprecation of PageLoader
The page loader has been deprecated in favor of the DynamicRoutes service.
registerUrlMatcher
→ see Register aDynamicRouteUrlMatcher
The Routable
types in the schema will eventually be deprecated, as
UrlMatchers
no longer use GraphQL
as they are decoupled into a service.
This also means that the route
query will be deprecated, if you still require
this functionality you can implement it using the DynamicRoutes
service:
import { createGraphQLRuntime } from "@front-commerce/core/graphql";
export default createGraphQLRuntime({
resolvers: {
Query: {
route: async (_, { url }, { services, loaders }) => {
const route = await services.DynamicRoutes.matchUrl(url);
if (!route) {
return null;
}
if (route.type === "category") {
return loaders.Category.load(route.params.id);
} else if (route.type === "product") {
return loaders.Product.load(route.params.id);
}
//...
},
},
},
});