npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@scayle/storefront-nuxt2

v7.39.28

Published

Nuxt2 integration for the SCAYLE Commerce Engine and Storefront API

Downloads

690

Readme

Storefront Core Nuxt2

npm version npm downloads License Nuxt

This is the basis for setting up a new Storefront based on Nuxt 2. It provides a set of useful configurations and reusable composables, that can be used in the actual shop project.

Getting started

To start working with storefront-core, make sure to register it as a module in your nuxt.config.ts file:

import { NuxtConfig } from '@nuxt/types'

const config: NuxtConfig & { storefront: ModuleOptions } = {
  // ...

  modules: ['@scayle/storefront-nuxt2/dist/module/register'],

  storefront: {
    stores: [
      {
        domain: 'www.aboutyou.de',
        basePath: '/',
        bapi: {
          host: 'https://api-cloud.aboutyou.de/v1/',
          shopId: 139,
        },
        redis: {
          host: process.env.REDIS_HOST || 'localhost',
          port: process.env.REDIS_PORT
            ? parseInt(process.env.REDIS_PORT, 10)
            : 6379,
        },
      },
    ],
    imageBaseUrl: 'https://cdn.aboutstatic.com/',
  },
}

export default config

You can see all the possible configuration options here. Basically you are able to configure the BAPI backend, from which URL images should be served, caching providers and so forth.

Architecture

The storefront-core comprises a multitude of functionality. Generally speaking, the most important are:

  • API middleware + RPC methods
  • Composable functions like useWishlist
  • Helper functions for managing shop data like attributes
  • Adaption of nuxt/image with custom provider to load images from About You CDNs

How do the RPC methods work?

The RPC method calls use a hybrid approach. When you are on the server, they directly call the corresponding function. When you are calling the RPC method on the client, the call is serialized and sent to the API endpoint, which calls the function and then returns the return values.

Example:

import { rpcCall } from './rpcCall'
import { rpcMethods } from './index'

const returnedValue = await rpcCall(rpcMethods.getProductById, { id: 12 })

The benefit of this approach is, that we can use the same interface, regardless if the function is currently called in a SSR context on the server or client-side in the Vue application. If you are interested in how this is handled, take a look at the implementation

List of composables

Core: useRpc

One of the most used composable, although you will probably not use this composable directly. It provides a useful wrapper around the rpcCall method mentioned above, adding loading states that can easily be used from within your components.

Example:

import { useRpc } from '@scayle/storefront-nuxt2'
import { rpcMethods } from './index'

const { fetching, data, fetch } = useRpc(rpcMethods.getProductById)

await fetch({ id: 12 })

useWishlist

Methods for managing the wishlist - adding and removing items, etc.

import { useWishlist } from '@scayle/storefront-nuxt2'

const { addItem, removeItem, clearWishlist, isInWishlist, fetch } =
  useWishlist()

await fetch()

await addItem({ variantId: 123 })

Logging

How to set up

There are two places where we can set up a log.

  1. Injected into the nuxt context
  2. Provided in the req(request)-object for middleware support

IMPORTANT: You can very simply adapt the implementation from demo-shop. To do so, please have a look at the /modules/log folder.

Log for nuxt context

Create a new nuxt plugin and run Log.attachToNuxtContext.

declare module '@nuxt/types' {
  interface Context {
    $log: Log
  }
}

export default defineNuxtPlugin((context, inject) => {
  Log.attachToNuxtContext(inject, new Log({
      space: 'exampleProject',
      handler: (entry: LogEntry) => { ... },
  }))
}

IMPORTANT: Make sure this plugin is initialized on client-side AND on server-side.

Log for server middleware (no nuxt context)

Create a new server-middleware that runs before core.

export default (req: any, res: any, next: any) => {
  Log.attachToRequest(
    req,
    new Log({
      space: 'exampleProject',
      handler: (entry: LogEntry) => { ... },
    })
  )
  next()
}

How to make sure it works

If the setup did not work, your log messages will have the default space: "global". So your terminal will look like this:

DEBUG [global.checkout.logout] Destroy session and redirect user

If the setup was correct, you should see something like this:

DEBUG [exampleProject.checkout.logout] Destroy session and redirect user

Features

Spaces

To better determine, where the log messages originated from, you can pass a space to the log, as well as create sub-spaces very easily.

const log = new Log({ space: 'myApp' })
const subLog = log.space('login')

subLog.error('User not found.') // ERROR [myApp.login] User not found.

// or simply do:
log.space('login').error('User not found.') // ERROR [myApp.login] User not found.

Time (Track execution time)

You can track execution time by using the .time(...) method from Log. Here is an example how to use it:

return await log.time(`RPC [${methodName}]`, async () => {
  return (await axios.post(url, params, { headers })).data as TResult
})

Console output will look like this:

[demo.sfc.rpc] RPC [getFilters] took 6ms
[demo.sfc.rpc] RPC [getProductsByCategory] took 10ms

Caching

Storefront Core supports two modes of Caching. In general all cached entries are namespaced by shopId.

SSR Cache

The whole rendered HTML of the server is stored inside redis and on each request we check if we have a cache HIT. If an entry is found the HTML is returned and no page has to be rendered.

Wrapped Cache

Another possibility is to cache downstream API calls, for example for requesting product information. This is heavily used in all RPC methods. They way this works is that the expensive function is simply wrapped by the cached() method, like this:

return await cached(bapiClient.products.getById)(options.id, {
  with: options.with,
  campaignKey,
  pricePromotionKey: options.pricePromotionKey,
})

The wrapping method takes care of calculating a cache key (based on the name of the function wrapped and the passed arguments). If no cache is found the underlying expensive function gets called and the returned value will be cached. If an entry is found, this will be returned instead omitting the expensive function call.

Disable caching of specific pages

There are multiple ways to control the caching feature. First of all, the cache is only used when it is enabled via config:

// inside nuxt.config.ts
  storefront: {
    // [...]
    cache: {
      enabled: true,
      ttl: 60 * 60,
      paths: [],
      pathsDisabled: [],
    },
    // [...]

The properties paths and pathsDisabled allow you to select in a white- and blacklist matter which pages to allow or disallow. If no paths are specified, all SSR requests will be cached.

Warning: Make sure that no user-related data is rendered ON the server! Make sure that either a) this is initialized on the client (like the wishlist/basket/user information in the navigation) or b) these pages are forbidden to get cached (like the account area)! Otherwise you will leak user data to other people.

Tagging of Cache

The cache allows you to associate tags to rendered pages. This way it is very easy to invalidate all rendered pages that contain an updated product, for example.

The best place for adding tags to the cache is inside the onFetchAsync method. Here is an example:

const { data, fetch } = useProduct()
const { $cache } = useContext()

onFetchAsync(async () => {
  await fetchProduct(id)
  $cache.addTags([`product:${id}`])
})

Internally this works by creating a SET entry per tag, including all the cached keys that contain this tag. Example: Keys 'abc', 'def' and 'xyz' are stored but only 'abc' and 'def' are tagged with 'product:1001'. Executing SMEMBERS 'tag:product:1001' will produce ['abc', 'def'].

Invalidation of Cache

The cache module automatically registers a middleware (<host>/_cache/) with endpoints that you can trigger externally. This allows you to register webhooks or other apps to issue POST requests.

Purge entire cache

POST <host>/_cache/purge/all

Purge the entire cache, including all cached downstream API requests (BAPI, etc.)

Purge a specific tag

POST <host>/_cache/purge/tags Body: ["product:1001"]

Purge all cache entries that are tagged with product:1001.

Cache-Control headers

The cache module allows to set Cache-Control headers, so that CDN's like Cloudflare can cache the page on the edge. The only supported mode is with smax-age and stale-while-revalidate. If no maxAge and staleWhileRevalidate is configured, no cache-control header will be sent:

// nuxt.config.ts
storefront: {
  // [...]
  cache: {
    enabled: true,
    ttl: 60 * 60,
    sendCacheControlHeaders: true,
    maxAge: 60 * 60,
    staleWhileRevalidate: 60 * 60 * 24,
  }
  // [...]
}