Vue Storefront is now Alokai! Learn More
Release notes v1.10.0

Release notes v1.10.0

Introduction

In this release we:

  • enabled configuration of cookie options in middleware.config.js file,
  • updated @vue-storefront/core and @vue-storefront/middleware to v2.7.5.

Migration guide

To configure cookie options, you should modify your middleware.config.js file:

module.exports = {
  integrations: {
    ct: {
      location: '@vsf-enterprise/commercetools-api/server',
      configuration: {
+       cookie_options: { 
+         'vsf-commercetools-token': {
+           httpOnly: true,
+           secure: true,
+           sameSite: 'lax'
+         }
+       },
        api: {
          ...
        },
        serverApi: {
          ...
        },
        currency: 'USD',
        country: 'US',
        languageMap: {
          en: ['en', 'de'],
          de: ['de', 'en']
        }
      },
      ...
    }
  }
};

By default, all cookie options are set to the most secure ones:

{
  httpOnly: true,
  secure: true,
  sameSite: 'strict'
}

More information can be found in Localization and cookies guide.

Upgrading to 1.2.0

Introduction

This migration guide helps developers using our commercetools integration to upgrade to version 2.3.0.

For more information about this version, refer to the Overview page.

Changes

  • Added E2E (cypress) tests:
    • use yarn test:e2e to start Cypress,
    • use test:e2e:hl to run Cypress tests in headless mode. Useful for running the tests in CI/CD workflows,
    • use test:e2e:generate:report to generate Cypress report.
  • BREAKING Fixed helpers used for search on the Category page to read phrase URL query string instead of term.
  • Added SfLoader to VsfShippingProvider to correctly handle the loading state.
  • Added expiration time to vsf-commercetools-token cookie matching token expiration time received from commercetools. Previously session cookies were used, which browsers delete when the current session ends. This forced users to log in more often than necessary.
  • Fixed an issue in the cart where API calls would result in HTTP 413 Payload Too Large error code due to large payload size. Cart method calls will now only include cart id and version instead of the whole cart object.

We also made changes to the following files:

  • updated components/Checkout/CartPreview.vue,
  • updated components/Checkout/UserBillingAddresses.vue,
  • updated components/Checkout/UserShippingAddresses.vue,
  • updated components/Checkout/VsfPaymentProviderMock.vue,
  • updated components/Checkout/VsfShippingProvider.vue,
  • updated components/MyAccount/BillingAddressForm.vue,
  • updated components/MyAccount/PasswordResetForm.vue,
  • updated components/MyAccount/ProfileUpdateForm.vue,
  • updated components/MyAccount/ShippingAddressForm.vue,
  • updated pages/Checkout/Billing.vue,
  • updated pages/Checkout/Payment.vue,
  • updated pages/Checkout/Shipping.vue,
  • updated pages/Checkout/ThankYou.vue.

Upgrading to 1.2.0-rc.2

Introduction

This migration guide helps developers using our commercetools integration to upgrade to version 2.3.0-rc.2.

For more information about this version, refer to the Overview page.

Changes

We made changes to the following files:

  • added new composable useBilling,
  • added new composable useShipping,
  • added new composable useShippingProvider
  • added new component components/Checkout/VsfShippingProvider,
  • added new component components/Checkout/VsfPaymentProviderMock,
  • added new components pages/Checkout/Billing.vue,
  • added new helper helpers/Checkout/getShippingMethodPrice.ts,
  • added new middleware middleware/is-authenticated.js,
  • added middleware.config.js,
  • updated components/Checkout/UserBillingAddresses.vue,
  • updated components/Checkout/UserShippingAddresses.vue,
  • updated composables/useUiHelpers/index.ts:
    • renamed changeSearchTerm to setTermForUrl,
    • added getSearchTermFromUrl,
  • updated translations in theme/lang,
  • updated middleware/checkout.js,
  • updated pages/Checkout.vue,
  • updated pages/Checkout/Payment.vue,
  • updated pages/Checkout/Shipping.vue,
  • updated icons in static/icons,
  • updated nuxt.config.js,
  • updated package.json,
  • deleted component pages/Checkout/OrderReview.vue,
  • deleted component pages/Checkout/PersonalDetails.vue.

Upgrading to 1.2.0-rc.3

Introduction

This migration guide helps developers using our commercetools integration to upgrade to version 2.3.0-rc.3.

For more information about this version, refer to the Overview page.

Changes

  • Fixed mapping for product attributes with type set.
  • Fixed cart getters to not throw errors when some properties are missing.
  • Fixed error thrown by the Composition API plugin when importing useWishlist.

We also made changes to the following files:

  • updated components/Checkout/VsfShippingProvider.vue,
  • deleted unused packages/commercetools/theme/helpers/filters/getFiltersForUrl.js,
  • deleted unused packages/commercetools/theme/helpers/filters/getFiltersFromUrl.js.

Upgrading to 1.3.0

Introduction

In 1.3.0 release, we introduce Forgot Password functionality and basic support for Stores and Channels in commercetools - you can now select a specific store and its corresponding channel, e.g., to display prices for a selected channel. We also fix some bugs related to Checkout forms and add new getters.

Changes

  • BREAKING Enable the purchase of an item with selected supply channel and distribution channel
  • Added storeGetters, useStore composable and plugin for creating locale, currency, and country cookies
  • Added forgotPasswordGetters and useForgotPassword composable
  • Added getProductSku getter to productGetters
  • Extended middleware.config.js with inventoryMode optional property - when added to the configuration, Inventory Mode is set on cart creation
  • Added smartphone only promo code input
  • Removed hardcoded link to the category in SearchResults.vue
  • Replaced mocked email address in MyProfile password change tab to active user email
  • Fixed bug with input characters limits on Checkout forms preventing submitting the form with a street name less than five characters long and apartment number with less than two characters long
  • Added e2e tests for Checkout

We also made changes to the following files:

  • Added pages/MyAccount/MyProfile.vue.
  • Updated components/MyAccount/BillingAddressForm.vue.
  • Updated components/MyAccount/ShippingAddressForm.vue.
  • Updated pages/Checkout/Billing.vue.
  • Updated pages/Checkout/Payment.vue.
  • Updated pages/Checkout/Shipping.vue.
  • Updated lang/de.js and lang/en.js.

Upgrading to 1.3.2

Introduction

In the 1.3.2 release, we focused on improving security by limiting permissions given to the customers to the bare minimum. This release doesn't include breaking changes, but we HIGHLY recommend applying the changes described below to your projects.

Changes

In the past, all requests sent to the commercetools included customer's access token created with scopes defined in the middleware.config.js file. If the customer didn't have permission to access a given resource, commercetools would return an error. It sounds logical - you can't access or operate the data if you don't have permission.

Unfortunately, some operations require manage_* scopes. For example:

  • resetting user password requires manage_customers scope,
  • adding product reviews requires manage_products scope.

Giving these permissions to the customer would allow them to access and modify all customers and products in your commercetools project.

For this reason, we introduced a new key named serverApi to the commercetools middleware configuration. You can use it to pass new server-specific API client credentials that middleware will use instead of customers API client for selected operations that require manage_* permissions.

// middleware.config.js
module.exports = {
  integrations: {
    ct: {
      location: '@vue-storefront/commercetools-api/server',
      configuration: {
        api: {
          uri: 'https://api.commercetools.com/PROJECT_KEY/graphql',
          authHost: 'https://auth.sphere.io',
          projectKey: 'PROJECT_KEY',
          clientId: 'CLIENT_ID', // Customer API client ID
          clientSecret: 'CLIENT_SECRET', // Customer API client secret
          scopes: [
            'create_anonymous_token:PROJECT_KEY',
            'manage_my_profile:PROJECT_KEY',
            'view_categories:PROJECT_KEY',
            'manage_my_payments:PROJECT_KEY',
            'manage_my_orders:PROJECT_KEY',
            'manage_my_shopping_lists:PROJECT_KEY',
            'view_published_products:PROJECT_KEY',
            'view_stores:PROJECT_KEY'
          ]
        },
        serverApi: {
          clientId: 'SERVER_ID', // Server API client ID
          clientSecret: 'SERVER_SECRET', // Server API client secret
          scopes: [
            'manage_customers:PROJECT_KEY',
            'manage_products:PROJECT_KEY'
          ]
        }
      }
    }
  }
};

API client used in the serverApi section can only have the manage_customers and manage_products scopes. Alternatively, you can use just the manage_project scope, but keep in mind that this gives Alokai unlimited access to everything in your project. When dealing with permissions, less is always better ;)

Use two separate API clients for api and serverApi

While it's possible to use the same API client in api and serverApi, but will different scopes, we recommend creating two separate API clients.

The image below shows a list of recommended scopes for the API client configured in the api section should have.

Image showing list of recommended scopes for the customers

The image below shows a list of recommended scopes for the API client configured in the serverApi section should have.

Image showing list of recommended scopes for the server

Upgrading to 1.3.3

Introduction

In the 1.3.3 release, we added new options to the serverApi introduced in the 1.3.2 release.

Changes

In the 1.3.2 release, we introduced a new key named serverApi to the commercetools middleware configuration. It stores API client used to generate access tokens for selected operations. However, we quickly noticed the need to allow adding other operations that will use these access tokens. That's why in this release we added new operations option to the serverApi configuration.

// middleware.config.js
module.exports = {
  integrations: {
    ct: {
      location: '@vue-storefront/commercetools-api/server',
      configuration: {
        // irrelevant configuration was omitted for readability
        serverApi: {
          clientId: 'SERVER_ID',
          clientSecret: 'SERVER_SECRET',
          scopes: [
            'manage_customers:PROJECT_KEY',
            'manage_products:PROJECT_KEY'
          ],
          operations: []
        }
      }
    }
  }
};

Custom operations might require additional scopes

Remember that custom operations added to the operations array might require additional scopes.

Example

Let's assume you have custom GraphQL that adds new mutation like shown below:

mutation AddProductType(
  $draft: ProductTypeDraft!
) {
  productType: createProductType(draft: $draft) {
    name
    description
    key
  }
}

In this case, you need to add createProductType to the operations array:

// middleware.config.js
module.exports = {
  integrations: {
    ct: {
      location: '@vue-storefront/commercetools-api/server',
      configuration: {
        // irrelevant configuration was omitted for readability
        serverApi: {
          operations: [
            'createProductType'
          ]
        }
      }
    }
  }
};

Upgrading to 1.4.0

Introduction

Start by updating the core packages

This guide only includes commercetools-specific changes. Follow the Migrating projects to 2.5.0 guide to update the core before you proceed with applying changes from this guide.

In this release, we focused on improving security and performance and updating GraphQL types.

This release includes some breaking changes. However, adjusting your projects shouldn't take too long and only requires a few minor changes.

Updated GraphQL types

You might have noticed that the @vue-storefront/commercetools-api package exports TypeScript types for GraphQL data returned by commercetools. We didn't update them for quite a while and decided that it was time to do it. There are plenty of new types as well as some breaking changes to the old types. Depending on the types you use, you might need to update your project.

Handling access tokens

We completely rewrote internals for handling authentication because of the poor performance of the old solution. Previously, the application requested a new access token several times for every user visiting the page if they didn't have the token cookie. This approach didn't make much sense, especially because all guests share the same access token until their session become unique in any way, for example, by logging in or adding an item to the cart or wishlist.

From now on, the application will request one access token for server operations (introduced in the previous release) and one for all guests. The server stores these tokens and doesn't send them out to the users. It will only create session cookies only for anonymous and logged-in users.

What is an anonymous session in commercetools?

You might be wondering what an "anonymous" session is and how it's different from the "guest" session. In commercetools there are three types of sessions:

  • guest - user that didn't log in nor perform any visitor-specific operation,
  • anonymous - user that didn't log in but performed some visitor-specific operation, for example, added item to the cart or wishlist,
  • customer - user that logged into the application.

This change by itself doesn't require any changes in existing projects. However, we used this opportunity to improve the security of the cookies storing the user session. See the next section for more details.

The commercetools integration creates the vsf-commercetools-token cookie to store the user session. In the past, this cookie didn't include the secure and httpOnly attributes. The lack of these attributes meant that the cookie could be sent with unsecured HTTP and be accessed using JavaScript, which exposed the cookie to security risks.

From now on, a session cookie will always have the httpOnly attribute. It will also have the secure attribute, but only if the request was HTTPS. For this reason, you don't have to use HTTPS in your development environments.

However, because the session cookie can no longer be accessed using JavaScript, we needed a new way of checking if the user is logged in. For this reason, we introduced a new isLoggedIn endpoint. You need to update the middleware/is-authenticated.js file, which protects /my-account routes from unauthorized access and all other parts of the application that read the session cookie.

// middleware/is-authenticated.js
import { Logger } from '@vue-storefront/core';

export default async ({ $vsf, route, redirect }) => {
  try {
    const isLoggedIn = await $vsf.$ct.api.isLoggedIn();

    if (!isLoggedIn) {
      throw new Error(`"${ route.fullPath }" route is only available to logged-in customers`);
    }
  } catch (error) {
    Logger.warn(error.toString());
    return redirect('/');
  }
};

In components, you can use the same approach by using the useVSFContext helper to access the $ct context.

// Vue components
import { useVSFContext } from '@vue-storefront/core';

export default {
  setup() {
    const { $ct } = useVSFContext();

    async function isLoggedIn() {
      return await $ct.api.isLoggedIn();
    }
  }
};

Other changes

Below is the list of the template files that we updated since the last release. You can generate a new project using our CLI and compare the files listed below with your existing project to see if they need updating.

  • components/AppHeader.vue,
  • components/CategoryPageHeader.vue,
  • components/MyAccount/ProfileUpdateForm.vue,
  • components/StoreLocaleSelector.vue,
  • composables/useUiHelpers/index.ts,
  • lang/de.js,
  • lang/en.js,
  • middleware/is-authenticated.js ,
  • nuxt.config.js,
  • package.json,
  • pages/Checkout.vue,
  • pages/Checkout/Billing.vue,
  • pages/Checkout/Payment.vue,
  • pages/Checkout/Shipping.vue,
  • pages/Checkout/ThankYou.vue,
  • pages/MyAccount/OrderHistory.vue,
  • pages/Product.vue.

If you are using E2E tests included in the projects, you might also need to update the following files.

  • tests/e2e/api/requests.ts,
  • tests/e2e/fixtures/responses/getOrders.json,
  • tests/e2e/fixtures/responses/getOrdersWithOffset.json,
  • tests/e2e/fixtures/test-data/e2e-add-to-cart.json,
  • tests/e2e/fixtures/test-data/e2e-carts-merging.json,
  • tests/e2e/fixtures/test-data/e2e-my-account-order-history.json,
  • tests/e2e/fixtures/test-data/e2e-product-page.json,
  • tests/e2e/fixtures/test-data/e2e-remove-from-cart.json,
  • tests/e2e/fixtures/test-data/e2e-update-cart.json,
  • tests/e2e/integration/e2e-add-to-cart.spec.ts,
  • tests/e2e/integration/e2e-carts-merging.spec.ts,
  • tests/e2e/integration/e2e-checkout-order-summary.spec.ts,
  • tests/e2e/integration/e2e-my-account-order-history.spec.ts,
  • tests/e2e/integration/e2e-my-account.spec.ts,
  • tests/e2e/integration/e2e-place-order.spec.ts,
  • tests/e2e/integration/e2e-user-login.spec.ts,
  • tests/e2e/integration/e2e-user-registration.spec.ts,
  • tests/e2e/pages/category.ts,
  • tests/e2e/pages/checkout.ts,
  • tests/e2e/pages/components/breadcrumbs.ts,
  • tests/e2e/pages/components/cart-sidebar.ts,
  • tests/e2e/pages/components/header.ts,
  • tests/e2e/pages/components/login-modal.ts,
  • tests/e2e/pages/my-account.ts,
  • tests/e2e/utils/network.ts.

Upgrading to 1.5.0

Introduction

In this release, we focused on improving the implementation of faceting. To be up-to-date with the latest features offered by commercetools, it no longer uses REST API but GraphQL API instead. It's also no longer a separate integration and is part of the commercetools integration.

We've also changed the organization's name under which we release the commercetools integration from @vue-storefront to @vsf-enterprise.

This release includes some breaking changes. However, adjusting your projects shouldn't take too long and only requires a few minor changes.

Update dependencies

Rename old dependencies

As stated above, we changed the organization's name under which we release commercetools integration. Open the package.json file and rename the following dependencies:

  • @vue-storefront/commercetools to @vsf-enterprise/commercetools
  • @vue-storefront/commercetools-api to @vsf-enterprise/commercetools-api

Add new and remove old packages

Firstly add @vsf-enterprise/commercetools-api package (if it's not already installed).

yarn add @vsf-enterprise/commercetools-api

Starting this release, we require a new dependency:

  • vue-lazy-hydration in version 2.0.0-beta.4.

We also no longer use the @vue-storefront/nuxt-theme dependency.

To update your project, run the following commands:

yarn add vue-lazy-hydration@~2.0.0-beta.4
yarn remove @vue-storefront/nuxt-theme

Update configuration files

middleware.config.js

  • Open middleware.config.js file and replace @vue-storefront/commercetools-api/server with @vsf-enterprise/commercetools-api/server for location inside ct section.
// middleware.config.js
module.exports = {
  integrations: {
    ct: {
-     location: '@vue-storefront/commercetools-api/server',
+     location: '@vsf-enterprise/commercetools-api/server',
      configuration: {
        // irrelevant configuration was omitted for readability
      }
    },
  }
};
  • Remove extensions line from ct section.
// middleware.config.js
module.exports = {
  integrations: {
    ct: {
      location: '@vsf-enterprise/commercetools-api/server',
-      extensions: existing => existing.concat('@vsf-enterprise/commercetools/extensions'),
      configuration: {
        // irrelevant configuration was omitted for readability
      }
    },
  • Remove ctf section. It will no longer be used as faceting is now part of commercetools integration.
// middleware.config.js
module.exports = {
  integrations: {
    // ct integration config
-   ctf: {
-     location: '@vsf-enterprise/ct-faceting/server',
-     configuration: {
-       api: {
-         authHost: 'https://auth.sphere.io',
-         projectKey: '<PROJECT_KEY>',
-         clientId: '<CLIENT_ID>',
-         clientSecret: '<CLIENT_SECRET>',
-         scopes: [
-           /* scope rules */
-         ]
-       },
-       faceting: {
-         host: 'https://api.commercetools.com'
-       }
-     }
-   }
  }
};

nuxt.config.js

  • Default configuration for faceting is no longer provided by external package @vsf-enterprise/ct-faceting. Now it is placed in nuxt.config.js under buildModules section and in @vsf-enterprise/commercetools/nuxt module. If you don't have default configuration or you need more details please refer to Configuration section of the useFacet composable.

Context configuration access

Since we're removing $ctf object from the middleware configuration, and still referring to the application context with it, now all the faceting configuration data will be available from the $ct key/object.

Example:

// before
const { $ctf: { config }} = useVSFContext();
// now
const { $ct: { config: { faceting } }} = useVSFContext();

Please be aware that with defining facets your commercetools data structure might be different, and reaching for specific objects might need some adjustments.

  • We no longer use the @vue-storefront/nuxt-theme package which registered default routing paths (we will remove it after). They need to be configured in the nuxt.config.js file.

To do it, use the extendRoutes function in the nuxt.config.js.

// nuxt.config.js
router: {
  // other configuration options
  extendRoutes(routes, resolve) {
    routes.push({
      name: 'home',
      path: '/',
      component: resolve(__dirname, 'pages/Home.vue')
    },
    {
      name: 'product',
      path: '/p/:id/:slug/',
      component: resolve(__dirname, 'pages/Product.vue')
    },
    {
      name: 'category',
      path: '/c/:slug_1/:slug_2?/:slug_3?/:slug_4?/:slug_5?',
      component: resolve(__dirname, 'pages/Category.vue')
    },
    {
      name: 'my-account',
      path: '/my-account/:pageName?',
      component: resolve(__dirname, 'pages/MyAccount.vue')
    },
    {
      name: 'checkout',
      path: '/checkout',
      component: resolve(__dirname, 'pages/Checkout.vue'),
      children: [
        {
          path: 'shipping',
          name: 'shipping',
          component: resolve(__dirname, 'pages/Checkout/Shipping.vue')
        },
        {
          path: 'billing',
          name: 'billing',
          component: resolve(__dirname, 'pages/Checkout/Billing.vue')
        },
        {
          path: 'payment',
          name: 'payment',
          component: resolve(__dirname, 'pages/Checkout/Payment.vue')
        },
        {
          path: 'thank-you',
          name: 'thank-you',
          component: resolve(__dirname, 'pages/Checkout/ThankYou.vue')
        }
      ]
    },
    {
      name: 'reset-password',
      path: '/reset-password',
      component: resolve(__dirname, 'pages/ResetPassword.vue')
    });
  }
},

Routes configuration can be moved to a separate file and imported into the nuxt.config.js file to increase the readability.

  • @vue-storefront/nuxt-theme package needs to be removed from buildModules section.
  buildModules: [
-   ['@vue-storefront/nuxt-theme'],
  • As faceting is now part of commercetools integration, the configuration inside buildModules is no longer needed and has to be removed.
  buildModules: [
-   ['@vsf-enterprise/ct-faceting/nuxt', {
-       apiConfigModule: '@vsf-enterprise/commercetools/nuxt'
-   }]
  • Update configuration of the @vue-storefront/nuxt module by replacing @vue-storefront/commercetools with @vsf-enterprise/commercetools in useRawSource object.
  ['@vue-storefront/nuxt', {
    useRawSource: {
      dev: [
        '@vue-storefront/core',
-       '@vue-storefront/commercetools'
+       '@vsf-enterprise/commercetools'
      ],
      prod: [
        '@vue-storefront/core',
-       '@vue-storefront/commercetools'
+       '@vsf-enterprise/commercetools'
      ]
    }
  }]

Update imports

The final step is to replace ALL @vue-storefront/commercetools imports with @vsf-enterprise/commercetools in *.vue template file.

- import { useUser } from '@vue-storefront/commercetools';
+ import { useUser } from '@vsf-enterprise/commercetools';

The same applies to @vue-storefront/commercetools-api, if it's used in the project. It has to be replaced with @vsf-enterprise/commercetools-api.

- import { Category } from '@vue-storefront/commercetools-api';
+ import { Category } from '@vsf-enterprise/commercetools-api';

Optional: Redis cache

In this version, we've added support for Redis cache out of the box for new projects. If you want to use it, please follow the instructions below. If not, you can skip this step.

You can read more about our implementation of Redis cache on SSR Cache page.

Add new packages

Start by installing required dependencies:

  • @vue-storefront/cache in version 2.5.3
  • @vsf-enterprise/redis-cache in version 1.3.0

To update your project, run the following commands:

yarn add @vue-storefront/cache@~2.5.3
yarn add @vsf-enterprise/redis-cache@~1.3.0

Add body parser server middleware

Create a body-parser.js file inside of the serverMiddleware directory with the following code:

// ./serverMiddleware/body-parser.js
const bodyParser = require('body-parser')
const app = require('express')()

app.use(bodyParser.json())
module.exports = app

Add the location of the body-parser.js file to the serverMiddleware array in nuxt.config.js.

serverMiddleware: [
  '~/serverMiddleware/body-parser.js'
],

Extend configuration

In the nuxt.config.js file, update the modules section by adding the @vue-storefront/cache/nuxt module with the following configuration.

  modules: [
    // other modules
    ['@vue-storefront/cache/nuxt', {
      enabled: process.env.VSF_REDIS_ENABLED === 'true',
      invalidation: {
        endpoint: process.env.CACHE_INVALIDATE_URL,
        key: process.env.CACHE_INVALIDATE_KEY,
        handlers: [
          '@vue-storefront/cache/defaultHandler'
        ]
      },
      driver: [
        '@vsf-enterprise/redis-cache',
        {
          // docs: https://github.com/luin/ioredis/blob/master/API.md#new-redisport-host-options
          redis: {
            keyPrefix: process.env.VSF_REDIS_KEY_PREFIX,
            host: process.env.VSF_REDIS_HOST,
            port: process.env.VSF_REDIS_PORT || 6379,
            password: process.env.VSF_REDIS_PASSWORD
          }
        }
      ]
    }]
  ],

Update template file

As the last step, update template files that use useCache composable. Start by adding an import statement:

import { useCache, CacheTagPrefix } from '@vue-storefront/cache';

Then, use the useCache composable in the setup function to add proper tags. Below is a code example of handling tags on the Category page.

setup() {
  const { addTags } = useCache();

  onSSR(() => {
    const tags = [{ prefix: CacheTagPrefix.View, value: 'category' }];
    const productTags = products.value.map((prod) => {
      return { prefix: CacheTagPrefix.Product, value: prod._id };
    });
    const categoriesTags = categoryTree.value.items.map((cat) => {
      return { prefix: CacheTagPrefix.Category, value: cat.id };
    });

    addTags(tags.concat(productTags, categoriesTags));
  });
}

Upgrading to 1.6.0

Introduction

In this release, we've migrated from the old internationalization package called nuxt-i18n to the new one called nuxtjs/i18n. Follow its Migration guide to upgrade your project. We've also refactored the useUserBilling and useUserShipping composables.

Updating the useUserBilling and useUserShipping implementations

The billing and shipping properties from the useUserBilling and useUserShipping composables reference the user object provided by the useUser composable.

For this reason, we are deprecating the load methods from the useUserBilling and useUserShipping composables. To populate all the billing and shipping properties with user data, call the useUser.load method.

import { onSSR } from '@vue-storefront/core'
import {
  useUser,
  useUserBilling,
  useUserShipping
} from '@vsf-enterprise/commercetools';

export default {
  setup() {
    const { load: loadUser } = useUser();
-   const { load: loadBilling, billing } = useUserBilling();
-   const { load: loadShipping, shipping } = useUserShipping();
+   const { billing } = useUserBilling();
+   const { shipping } = useUserShipping();

    onSSR(async () => {
      await loadUser();
-     await loadBilling();
-     await loadShipping();
    });
  
    return {
      billing,
      shipping
    };
  }
};

Calling the useUserBilling.load and useUserShipping.load methods is no longer necessary and will result in a deprecation warning.

Upgrading to 1.7.0

Introduction

Hi. With this version, we're introducing some significant performance optimization like new improved typings, the new Node version or conditional rendering removal, finally build modern mode for client.

Upgrade to Node 12 or 14

We have recommended using Node 12 or higher for a few months now. It's mandatory starting with this release because @vsf-enterprise/commercetools-api and @vsf-enterprise/commercetools packages are now targeting ES2019 syntax, supported fully in Node 12+.

At this moment, we don't support Node versions higher than 14.

Update import paths of types generated from commercetools GraphQL API

You might have noticed that some of the types exported from the @vsf-enterprise/commercetools-api represent data accepted and returned from the commercetools GraphQL API. We thought it would be easier to differentiate our types from those generated from the API if they come from two different packages.

For this reason, we introduced the new @vsf-enterprise/commercetools-types package. It only contains types generated from the commercetools GraphQL API and nothing more.

The @vsf-enterprise/commercetools-api package only includes types for the Server Middleware API, such as parameters accepted and data returned from API endpoints and default GraphQL queries sent to the commercetools GraphQL API.

You can refer to the API Reference for more details.

- import type { Customer } from '@vsf-enterprise/commercetools-api`;
+ import type { Customer } from '@vsf-enterprise/commercetools-types`;

Update deprecated types

We refactored most types in @vsf-enterprise/commercetools-api and @vsf-enterprise/commercetools packages to improve coding suggestions. We marked most of the types we no longer use as deprecated with comments indicating the new type that you should use instead. Most IDEs should highlight deprecated types — you can click them to go to the type declaration file for more information.

We will remove deprecated types in version 1.7.0.

Upgrade @storefront-ui/vue package to version 0.13.0

With the new Storefront UI version, we bring some performance improvements however, you need to make some changes to your components.

# components/CartSidebar

  <SfSidebar
   class="sf-sidebar--right"
   v-e2e="'sidebar-cart'"
   :visible="isCartSidebarOpen"
   :title="$t('My Cart')"
   @close="toggleCartSidebar"
   :persistent="hasNotifications"
+  position="right"
  >
# components/WishlistSidebar

  <SfSidebar
   class="sf-sidebar--right"
   v-e2e="'sidebar-wishlist'"
   :visible="isWishlistSidebarOpen"
   :title="$t('My wishlist')"
   @close="toggleWishlistSidebar"
   :persistent="hasNotifications"
+  position="right"
  >

Remove mobileObserver

With this version we're getting rid of all the conditional rendering. This means that we're not using mobileObserver utility anymore. It was removed along with the new Storefront UI version. Please follow these steps to cover all the necessary changes. This contributes to our full CDN optimization.

# components/AppFooter

data() {
  return {
-  isMobile: false,
-  desktopMin: 1024
  }
}
# components/AppHeader

 <template #navigation>
- <HeaderNavigation :isMobile="isMobile">
+ <HeaderNavigation>
 </template>

...

- import {
-  mapMobileObserver,
-  unMapMobileObserver
- } from '@storefront-ui/vue/src/utilities/mobile-observer.js';

...

- const isMobile = ref(mapMobileObserver().isMobile.get());

...

watch(() => term.value, (newVal, oldVal) => {
-  const shouldSearchBeOpened = (!isMobile.value && term.value.length > 0) && ((!oldVal && newVal) || (newVal.length !== oldVal.length && isSearchOpen.value === false));
-  if (shouldSearchBeOpened) {
+  if (term.value.length > 0 && ((!oldVal && newVal) || (newVal.length !== oldVal.length && isSearchOpen.value === false))) {
    isSearchOpen.value = true;
  }
});

...

- onBeforeUnmount(() => {
-  unMapMobileObserver();
- });

...

return {
  ...
- isMobile  
}
# components/Headernavigation

  <template>
-  <div class="sf-header__navigation desktop" v-if="!isMobile">
-   <SfHeaderNavigationItem
-     v-for="category in categories"
-     :key="category.id"
-     class="nav-item"
-     v-e2e="`app-header-url_${category.slug}`"
-     :label="category.name"
-     :link="localePath(`/c/${category.slug}`)"
-   />
-  </div>
-  <SfModal v-else :visible="isMobileMenuOpen">
+   <div class="header-navigation">
+    <div class="sf-header__navigation desktop-only">
+      <SfHeaderNavigationItem
+        v-for="category in categories"
+        :key="category.id"
+        class="nav-item"
+        v-e2e="`app-header-url_${category.slug}`"
+        :label="category.name"
+        :link="localePath(`/c/${category.slug}`)"
+      />
+    </div>
+   <SfModal class="smartphone-only" :visible="isMobileMenuOpen">
      <SfList>
        <SfListItem
          v-for="category in categories"
          :key="category.id"
          class="nav-item sf-header-navigation-item"
          v-e2e="`app-header-url_${category.slug}`"
        >
          <SfMenuItem
            :label="category.name"
            class="sf-header-navigation-item__menu-item"
            :link="localePath(`/c/${category.slug}`)"
            @click.native="toggleMobileMenu"
          />
        </SfListItem>
      </SfList>
    </SfModal>
+  </div>
  </template>

...

- props: {
-  isMobile: {
-   type: Boolean,
-   default: false
-  }
- },
# components/InstagramFeed

  <template>
...
-   <SfImage v-if="isMobile" :src="'/homepage/imageAm.webp' | addBasePathFilter" alt="katherina_trn" :width="160" :height="160">katherina_trn</SfImage>
-   <SfImage v-else :src="'/homepage/imageAd.webp' | addBasePathFilter" alt="katherina_trn" :width="470" :height="470">katherina_trn</SfImage>
+   <SfImage
+     class="smartphone-only"
+     :src="'/homepage/imageAm.webp' | addBasePathFilter"
+     alt="katherina_trn"
+     :width="160"
+     :height="160"
+   >
+    katherina_trn
+   </SfImage>
+   <SfImage
+     class="desktop-only"
+     :src="'/homepage/imageAd.webp' | addBasePathFilter"
+     alt="katherina_trn"
+     :width="470"
+     :height="470"
+   >
+    katherina_trn
+   </SfImage>

...

-   <SfImage v-if="isMobile" :src="'/homepage/imageBm.webp' | addBasePathFilter" alt="katherina_trn" :width="160" :height="160">katherina_trn</SfImage>
-   <SfImage v-else :src="'/homepage/imageCd.webp' | addBasePathFilter" alt="katherina_trn" :width="470" :height="470">katherina_trn</SfImage>
+   <SfImage
+     class="smartphone-only"
+     :src="'/homepage/imageBm.webp' | addBasePathFilter"
+     alt="katherina_trn"
+     :width="160"
+     :height="160"
+   >
+    katherina_trn
+   </SfImage>
+   <SfImage
+     class="desktop-only"
+     :src="'/homepage/imageCd.webp' | addBasePathFilter"
+     alt="katherina_trn"
+     :width="470"
+     :height="470"
+   >
+    katherina_trn
+   </SfImage>

...

-   <SfImage v-if="isMobile" :src="'/homepage/imageCm.webp' | addBasePathFilter" alt="katherina_trn" :width="160" :height="160">katherina_trn</SfImage>
-   <SfImage v-else :src="'/homepage/imageBd.webp' | addBasePathFilter" alt="katherina_trn" :width="470" :height="470">katherina_trn</SfImage>
+   <SfImage
+     class="smartphone-only"
+     :src="'/homepage/imageCm.webp' | addBasePathFilter"
+     alt="katherina_trn"
+     :width="160"
+     :height="160"
+   >
+    katherina_trn
+   </SfImage>
+   <SfImage
+     class="desktop-only"
+     :src="'/homepage/imageBd.webp' | addBasePathFilter"
+     alt="katherina_trn"
+     :width="470"
+     :height="470"
+   >
+    katherina_trn
+   </SfImage>

...

-   <SfImage v-if="isMobile" :src="'/homepage/imageDm.webp' | addBasePathFilter" alt="katherina_trn" :width="160" :height="160">katherina_trn</SfImage>
-   <SfImage v-else :src="'/homepage/imageDd.webp' | addBasePathFilter" alt="katherina_trn" :width="470" :height="470">katherina_trn</SfImage>
+   <SfImage
+     class="smartphone-only"
+     :src="'/homepage/imageDm.webp' | addBasePathFilter"
+     alt="katherina_trn"
+     :width="160"
+     :height="160"
+   >
+    katherina_trn
+   </SfImage>
+   <SfImage
+     class="desktop-only"
+     :src="'/homepage/imageDd.webp' | addBasePathFilter"
+     alt="katherina_trn"
+     :width="470"
+     :height="470"
+   >
+    katherina_trn
+   </SfImage>
  </template>

...

- import {
-  mapMobileObserver,
-  unMapMobileObserver
- } from '@storefront-ui/vue/src/utilities/mobile-observer.js';

...

-  computed: {
-    ...mapMobileObserver()
-  },
-  beforeDestroy() {
-    unMapMobileObserver();
-  }
# pages/MyAccount

- import { computed, onBeforeUnmount, useContext, useRouter, useRoute } from '@nuxtjs/composition-api';
+ import { computed, useContext, useRouter, useRoute } from '@nuxtjs/composition-api';
- import {
-  mapMobileObserver,
-  unMapMobileObserver
- } from '@storefront-ui/vue/src/utilities/mobile-observer.js';

...

- const isMobile = computed(() => mapMobileObserver().isMobile.get());

const activePage = computed(() => {
  const { pageName } = route.value.params;
  if (pageName) {
    return (pageName.charAt(0).toUpperCase() + pageName.slice(1)).replace('-', ' ');
  }
-  else {
-    return !isMobile.value ? i18n.t('My profile') : '';
-  }
});

-  onBeforeUnmount(() => {
-   unMapMobileObserver();
-  });

Use --modern=client mode

From now on, with application build we're using Nuxt modern mode just for client side. More about this you can find here.

# package.json

- "build": "nuxt build -m",
- "build:analyze": "nuxt build -a -m",
- "start": "nuxt start",
+ "build": "nuxt build --modern=client",
+ "build:analyze": "nuxt build -a --modern=client",
+ "start": "nuxt start --modern=client",

Upgrading to 1.8.5

Introduction

In the 1.8.5 release, we changed the response of productsSearch to return the raw data whenever customQuery is passed as parameter.

Changes

Previously, productsSearch needed the enhanceProduct parameter to handle customQuery responses but this fail to work due to the implementation of the productsSearch method that returns enhancedProductResponse.data.

  return (enhancedProductResponse.data as any)._variants;

This would fail if the enhanceProduct parameter did not properly format the data needed to create the response of the productsSearch which is not documented correctly.

With this we created this change to directly return the raw response coming from context.$ct.api.getProduct(<searchParams>, <customQuery>)

The change will not require everyone to pass in enhanceProduct and just allow them to modify or process the response after they received it.

Usage

Before

Previously, enhanceProduct was part of productsSearch and modify the data prior to being returned.

  const { search, products } = useProduct()

  // Which will fail cause of the required format of response of enhanceProduct
  await search({
    customQuery: { products: 'my-custom-query' },
    enhanceProduct: (productResponse) =>  productResponse
  })

Now

Now, since the productsSearch will now return raw response whenever there's a customQuery present. You may be able to add a method like enhanceProduct below that can transform the raw response to what's needed.

  const { search, products } = useProduct()

  const enhanceProduct = (productResponse) => productResponse

  await search({
    customQuery: { products: 'my-custom-query' }
  })

  products.value = enhanceProduct(products.value)

Upgrading to 1.9.0

Introduction

The main and the most important change was implementation of the init function for a performance boost and rework of the token flow logic, which was a necessary step after the usage of the init function.

Init function

Previously, Apollo Client was created on every request which was not a very good solution in terms of performance. Utilizing the new core mechanism we implemented the init function to bootstrap the client once on the build time.

FileWhat was changed
! packages/api-client/src/index.server.tsMove client creation logic from the onCreate to the init function

For a reference please check getInitConfig in the middleware package.

Token flow

Due to tokens leaks and security issues after the implementation of the init function, token flow was reworked.

FileWhat was changed
! packages/api-client/src/token/TokenManagerFactory.tsCreates object with helpers for reading and creating access token cookies and accessing current token provider
! packages/api-client/src/token/tokenParser.tsParses raw token string into an object
! packages/api-client/src/links/**All Apollo Client links related factories and handlers
! packages/api-client/src/links/createLinks.tsMain entry point for links creation

Token Flow Diagrams

Application bootstrapReequest flow overviewRequest handlerBefore auth handlerAfter auth handler