@noths/global-components-v2

A set of global components for NOTHS.

Usage no npm install needed!

<script type="module">
  import nothsGlobalComponentsV2 from 'https://cdn.skypack.dev/@noths/global-components-v2';
</script>

README

NOTHS Global Components With Contentstack

npm version Depfu Depfu

Frontend global components used at www.notonthehighstreet.com

Background & context

The purpose of this package is to redevelop the old global-components from vanilla js to React Components that listen to the Contentstack API. The development of the components is split into middleware and presentational components because not all services require both instruments. For example, the Mononoth handles the fetching of Contentstack data itself so only requires the presentational component.

Provided below are branches on how global-components-V2 are integrated in each service.

Components included

  • NOTHS Navigation

Global components are components which will be on most pages of the site. except checkout.

The presentational components such as the Navigation are built using react, styled-components & ramda (Ramda was an optional choice, as a helper to assist with data sorting and passing down the datastucture as props to the presentational component)

The Middleware uses Contentstack and requires React 16.9.0+ or higher. The reason for this is due to the use of Context API that react includes in the DOM. This helps rule out other state management packages such a redux as a dependency.

Commit History integrating new navigation in other services:

SSR Webpack & Express App

https://github.com/notonthehighstreet/cms-frontend/commit/a1108ac92ccd323cc0dbfac0738cce434f26918b https://github.com/notonthehighstreet/departments-frontend/commit/1d9a800e44c1cfe0f3cb80c40a1cf7d995f5d47f https://github.com/notonthehighstreet/product-listings/commit/59d103664001122c7c7242bc06e8baa7c3b5c093

Client side only

https://github.com/notonthehighstreet/user-account/commit/a17b4d800365a2b512111054074f2972d17401dd https://github.com/notonthehighstreet/favourites/commit/feb3c936597328ab0d0adc94604be8200c5d870b

Next JS App

https://github.com/notonthehighstreet/user-account-frontend/commit/3cf54e48cd460f6f79df253eadac2298c42c74bd

Hypernova & Mononoth

https://github.com/notonthehighstreet/hypernova-app/commit/20f3598196537e7360b5850986ca9cfbe204f766 https://github.com/notonthehighstreet/notonthehighstreet/commit/505b5ef8e1e9016eb216a426c069cfc4fed0cc27 https://github.com/notonthehighstreet/notonthehighstreet/commit/926c1fd948b0972d10b7a7c758113eed12b93f97

React

import React, { Component } from 'react';
import { GlobalDataContext, Navigation } from '@noths/global-components-v2';

class App extends Component {
  render() {
    return (
      <GlobalDataContext.Consumer>
        {({ navData }) => (
          <>
            <Navigation navData={navData} />
            <MainContent>
          </>
        )}
      </GlobalDataContext.Consumer>
    );
  }
}

Server - Express Middleware

import express from 'express';
import { globalComponentsDataFetcher, GlobalDataContext } from '@noths/global-components-v2';

const appServer = express();

...

const renderApp = async (req, res) => {
  const navData = await globalComponentsDataFetcher('qa', 'navigation');

  Promise.all().then(() => {
    const rootApp = renderToString(
      <GlobalDataContext.Provider value={{ navData }}>
        <App />
      </GlobalDataContext.Provider>
    );

    const renderHtml = () => {
      return `
        <!DOCTYPE html>
        <html lang='en'>
          <head>
            <meta charset="UTF-8">
          </head>
          <body>
            <div id="root">${rootApp}</div>
            <script type="text/javascript">window.GLOBAL_DATA = ${navData}</script>
          </body>
        </html>
      `
    }

    res.send(rootApp);
  });
}

appServer.use(renderApp);

Client - Hydrating the App

import React from 'react';
import { hydrate } from 'react-dom';

import App from './App';

// Grab the state from a global variable injected into the server-generated HTML
const preloadedGlobalState = window.GLOBAL_DATA;

// Allow the passed state to be garbage-collected
delete window.GLOBAL_DATA;

hydrate(
  <GlobalDataContext.Provider value={{ navData: preloadedGlobalState }}>
    <App />
  </GlobalDataContext.Provider>,
  document.getElementById('root')
);

Reason for calling window.GLOBAL_DATA was the intention of adding footer also. Therefore making it Global.

Also add the css below. This should be a temporary measure until the burger menu from header in the old global-components as the burger menu lives in the header.

  @media only screen and (max-width: 47.97917em) {
    [global-component].gc-header .gc-header-menu {
        position: absolute;
        left: 0;
        display: none;
    }
  }

Contentstack

The stack in which the Navigation Data and future Global Components Data should stay in is the "Global Components Stack"

https://app.contentstack.com/#!/stack/blt6444b9076ec27f02/dashboard

https://www.contentstack.com/docs/platforms/javascript-browser

https://www.contentstack.com/docs/platforms/javascript-browser/api-reference/

Confluence:

https://notonthehighstreet.atlassian.net/wiki/spaces/T5D/pages/756776961/Contentstack+resources . This will have the details of who to contact should you ever need more support around contentstack.

Note:

  • Contentstack has three environments live (changes visible on the live site), preview (used by content editors to test changes) and qa (used by devs to test changes)
  • The live and preview environments run on the same container

Cacheing

All services

  • The Contentstack response is cached for x seconds (see notonthehighstreet and node services). Only data from the 'live' Contentstack environment is cached.

notonthehighstreet

  • notonthehighstreet caches the response from Hypernova which contains the presentational component and uses the Contentstack response as the cache key
  • When installing a new version of the presentational component in Hypernova, in order to see the updated component rendered in notonthehighstreet you will need to bust the cache by re-publishing or editing the entry in Contentstack

Past issues

  • Fastly, Contentstack's CDN had slow response times, causing the whole site to slow down. There is now a 5s timeout in place in services for calls to third parties.

Contentstack Navigation Synthetics

https://app.datadoghq.com/synthetics/details/3wm-z9h-gr3

Prerequisites

  • NodeJS 8

Setup & run

Install dependencies

npm install

Run Dev Mode

npm run dev

Build Production.

npm run build

NOTE: The build produces two sources. Within the build directory after running build, it will produce build/index.js & build/components.js. This was done distinctively to cater for hypernova & mononoth where the hypernova app would only need the presentational component component.js where as the normal Node & React Apps would point to index.js as they would require the middleware.

Use the externals for production builds in your app.

  <script type="text/javascript" src="https://unpkg.com/react@{ ___VERSION___ }/umd/react.production.min.js"></script>
  <script type="text/javascript" src="https://unpkg.com/react-dom@{ ___VERSION___ }/umd/react-dom.production.min.js"></script>
  <script type="text/javascript" src="https://unpkg.com/contentstack@{ ___VERSION___ }/dist/web/contentstack.js"></script>
  <script type="text/javascript" src="https://unpkg.com/styled-components@{ ___VERSION___ }/dist/styled-components.js"></script>

External CDN Url

The package exposes an externalCDNUrl string which points to the package script hosted on unpkg. As global-components-v2 is bundled in all consumer facing frontend services at NOTHS it makes sense to externalise the package so that users will only download the bundle once per version of the package. The external cdn url should be included after react, react-dom and styled-components

Usage

Webpack production config:

externals: {
  '@noths/global-components-v2': 'GlobalComponentsV2',
}

Html template:

import { externalCDNUrl } from '@noths/global-components-v2';

<html>
  <head>
    ...
    <script type="text/javascript" src={externalCDNUrl} />
  </head>
</html>

Testing

There are two different types of tests in the project.

Unit tests

Unit tests use Jest and Enzyme.

To Run Unit Test

npm run test

Feature Test

Feature test uses Cypress.

NOTE: this has been set up as manual process for this time being. Reason for this was due to frontend pacakages is still in early stages of integrating cypress into the CI or if we decided to take a different approach with a new tech.

To run feature tests:

npm run test:dev:features

Stats

To ensure the bundle size for what is compiled is sustainable for performance and file size is reasonable, you can use the visual bundle analyzer made by https://chrisbateman.github.io/webpack-visualizer .

To view this, run a build and a the stats.html should be in the folder as follows - build/stats.html

Screen Shot 2019-08-27 at 4 19 08 PM

Releases

The project is hosted on NPM with the name @noths/global-components-v2.

TODO