@noths/mono-components

components that can only be used in the mononoth

Usage no npm install needed!

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

README

mono-components

Framework Toolchain Commands

About this package

This package is only used in the mononoth, through an automated hypernova integration.
See the related hypernova-app repo.

image

Description

General Aim

The Aim of this package is to have a point of contact between the shared-components package and the Mononoth to facilitate the disintegration of existing Mononoth-pages and allow for progressive iteration of new functionality.

Most of the development happens in shared-components, where we iterate on React components that can be shared between all frontend-services and the mononoth. However, there will be a number of functions that should not be part of shared-components because they will only ever be used in the Mononoth. This package takes responsibility for owning that functionality.

End Goal

mono-components should never be seen as a place where functionality will reside forever.

The aim of adding functionality inside of this package is to evolve components from shared-components so that they can be used in a disintegrated page as well other frontend-services.

Once the affected page is disintegrated into a frontend-service, any associated functionality in mono-components ceases to exist. At which point the relevant page is also removed from the mononoth.

Noths Stream Ownership

Owned by all Streams

Owned Routes or Domains

Routes aren't "owned" but this package affects the following routes in the Mononoth.

Consumer:

  • /search (and all related search pages: category, partner, departments)
  • /checkout

API Dependencies

none

Used NOTHS NPM Packages

  • @noths/global-components
  • @noths/shared-components
  • @noths/code-style
  • @noths/eslint-config

Installation & Development

Installation

# Install dependencies
npm i

Developing in mono-components only

# Run in dev mode (watches files & hot-reloads client-code)
npm run dev

Hybrid Development with shared-components & mono-components

Preparation:

# In frontend-packages root folder, link all packages together
# If you run into issues, you can try `npm run clean` before bootstrapping
npm run bootstrap

# A second method is linking only shared-components with mono-components
cd packages/shared-components
npm link
cd ../mono-components
npm link @noths/shared-components

Next up, you'll want to watch shared-component changes while developing in mono-components.

In terminal 1 (mono-components):

cd packages/mono-components
npm run dev

In terminal 2 (shared-components):

cd packages/shared-components
npm run watch

Now, if you make changes to something inside shared-components, after some time it should hot-reload in your browser. If you watch the logs in both terminals you should see terminal 2 update and then terminal 1 and finally the browser.

Hybrid Development with Mononoth & mono-components & shared-components

Preparation:

NOTE:

Don't link with lerna during this process!

This method is in an experimental state for and all the node_modules removing might be overkill, if you can improve this method, be my guest! Make sure your suggested method really works, then make a PR :)

# This will remove all node_modules and package-lock.json files.
# It will make sure you're on a completely clean base for development
cd packages/shared-components
npm run experimental:hardlink:rome

cd ../mono-components
npm run experimental:hardlink:rome

# Now in the `hypernova-app`-repo root, run the same command as well
npm run experimental:hardlink:rome

Any generated package-lock-files should not be commited.

Next up, you'll want to watch shared-component changes while developing in the Mononoth.

This is quite slow, so only do this when necessary. It is recommended to do most of your development in mono-components using the first Hybrid approach. That approach offers hot-reload and much faster development.

In terminal 1 (notonthehighstreet):

  • change the file notonthehighstreet/config/application/defaults/other.yml (uncomment/recomment the lines concerning hypernova)
  • Run the required command(s) to start the mononoth.

In terminal 2 (hypernova-app):

# This will have to be restarted if you want to get rid of the content mistmatch error
# after making changes to either mono-components or shared-components
npm run start:dev

In terminal 3 (shared-components):

# This will watch the files on any changes
npm run watch

In terminal 4 (mono-components):

# This will build the files only once (watch-mode doesn't exist in mono-components)
npm run build

For information about dev-toolkit, refer to the documentation.

Development Workflow in mono-components

  1. Add a new page
# make folder where components will reside
mkdir ./src/client/pages/my_page
# create a template where components will be rendered
touch ./src/server/views/my_page.hbs

And add the following content to my_page.hbs

<!-- NOTE: This template is for development only and will not be used in production. -->

<!-- Rendered Component HTML -->
{{{ renderComponent 'MyPage_ExampleComponent' props='{ ... }' }}}

<!-- CSS for faking component placement in the Mononoth -->
<style>
  .MONO-MyPage_ExampleComponent{
    position: relative;
    display: block;
    float: left;
    border: 2px dashed #42A5F5;
  }
</style>
  1. Add a new component
mkdir ./src/client/pages/my_page/ExampleComponent
touch ./src/client/pages/my_page/ExampleComponent/index.js
  1. Add a new component to ./src/client/index.js
export { default as MyPage_ExampleComponent } from './pages/my_page/ExampleComponent';

  1. Add new page route to ./src/settings.js
  { route: '/my_page', components: ['ExampleComponent'] },

You're now ready to start developing your ExampleComponent.

npm run dev

How to use in the Mononoth

You will only be able to use components in the Mononoth that have been defined in src/client/index.js.

In order to use these components, you will need to:

  • publish the @noths/mono-components package
  • bump the version of the package in hypernova-app
  • create a PR with the version bump
  • verify that everything works as expected in QA
  • release to production by merging the PR

In order to test your components in development with the Mononoth after bumping the version in hypernova-app, you will need to uncomment/comment-out the lines in the mononoth that specifies where the integration file will be served from before starting the Mononoth and the hypernova-app server.

Main steps of how to render a component in the Mononoth:

In your .erb view file:

<%= render_react_component(
    'Search_Sidebar',
    exampleProp: 'exampleText',
    filters: { ... }
) if hypernova_enabled? %>

At the end of your view layout file:

<%= include_hypernova_integration_script %>

In the controller that renders your view using the above layout:

class ExampleController
  include HypernovaRenderHelper
  around_action :hypernova_render_support, if: :hypernova_enabled?
  ...
end

A note on caching

You must ensure that your rendered mono-component is not inside a fragment-cached block. These blocks usually start with the <% cache-prefix.

You might also want to look into other forms of Rails caching such as "Action Caching".

In essence, you must "carve out" a path to your component that will not be cached through Rails in any way. The handling of component cache is done in hypernova itself.

A note on component-props

In order to render your component correctly, you will need to collect some data from the Mononoth to pass into your component as props. How to get this data will vary, it is suggested you find someone who is comfortable in working with Rails to help with this.

How to test

It is a bit difficult to test hypernova renders since they are async. We can however do some basic checks.

require 'feature_spec_helper'

# The view that renders your component
describe 'shared/_search_sidebar', type: :view do

  # Specify what the name of the component is
  let(:component_name) { "Search_Sidebar" }

  # Specify the props that you expect your component to receive
  let(:props) do
    {
      exampleProp: 'exampleText',
      filters: { ... }
    }
  end

  # Specify a placeholder that will be returned to make sure that something will get rendered
  let(:component_placeholder) { "dummy text" }

  before(:each) do
    # We need this to render the view
    allow(view).to receive(:current_filters).and_return({})
    allow(view).to receive(:filter_groups).and_return({ price: [] })

    # Mock test helpers
    allow(view).to receive(:render_react_component).with(component_name, props).and_return(component_placeholder)
  end

  # Example tests with feature flag on and off

  context "with feature flag on" do
    it do
      # turn the flag on
      allow(view).to receive(:feature_on?).with(:hypernova).and_return(true)
      render
      expect(view).to have_received(:render_react_component).with(component_name, props)
      expect(rendered).to include(component_placeholder)
    end
  end

  context "with feature flag off" do
    it do
      render
      expect(view).not_to have_received(:render_react_component).with(component_name, props)
      expect(rendered).not_to include(component_placeholder)
    end
  end
end

Building for Production

# Generate build for publishing npm package
npm run build

Publishing

Release version

Please refer to the frontend-packages Readme for the release process of patch, minor or major versions.

Releasing a Beta version

While logged into npm with your username, run:

npm run prerelease

Testing & Code-Style

# Run unit tests
npm test

# Format all files using prettier settings
npm run format

# Lint all javascript files
npm run lint

Feature-Test Workflow

none as of now

Error Logging

No logging is being used in this package.

CI

CI is done via lerna in frontend-packages root

Analytics

No Analytics is being done in this package.