npm package: @noths/mono-components
Description: components that can only be used in the mononoth
Latest version: 2.0.3

## mono-components
 ![Framework](https://img.shields.io/badge/Framework-React-green.svg?longCache=true) ![Toolchain](https://img.shields.io/badge/Toolchain-dev--toolkit-green.svg?longCache=true) ![Commands](https://img.shields.io/badge/Commands-Standard-green.svg?longCache=true)

### About this package

This package is only used in the mononoth, through an automated [hypernova](https://github.com/airbnb/hypernova) integration. <br>
See the related [hypernova-app](https://github.com/notonthehighstreet/hypernova-app) repo.

![image](https://user-images.githubusercontent.com/3392110/43467787-6cfbf110-94da-11e8-83e7-aa076f215a84.png)

### Description

#### General Aim
The Aim of this package is to have a point of contact between the `shared-components` package and the [Mononoth](https://github.com/notonthehighstreet/notonthehighstreet) 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
```bash
# 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:**
```bash
# 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):**
```bash
cd packages/mono-components
npm run dev
```
**In terminal 2 (shared-components):**
```bash
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!<br><br>
> 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 :)
```bash
# 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):**
```bash
# 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):**
```bash
# This will watch the files on any changes
npm run watch
```
**In terminal 4 (mono-components):**
```bash
# This will build the files only once (watch-mode doesn't exist in mono-components)
npm run build
```

---

For information about [dev-toolkit](https://github.com/stoikerty/dev-toolkit), refer to [the documentation](https://github.com/stoikerty/dev-toolkit/blob/master/docs/templates.md).

### Development Workflow in mono-components

1. Add a new page
```bash
# 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`
```handlebars
<!-- 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>
```

2. Add a new component
```bash
mkdir ./src/client/pages/my_page/ExampleComponent
touch ./src/client/pages/my_page/ExampleComponent/index.js
```

3. Add a new component to `./src/client/index.js`
```js
export { default as MyPage_ExampleComponent } from './pages/my_page/ExampleComponent';

```

4. Add new page route to `./src/settings.js`
```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](https://github.com/notonthehighstreet/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](https://github.com/notonthehighstreet/notonthehighstreet/blob/master/config/application/defaults/other.yml#L140) 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:
```erb
<%= render_react_component(
    'Search_Sidebar',
    exampleProp: 'exampleText',
    filters: { ... }
) if hypernova_enabled? %>
```

At the end of your view layout file:
```erb
<%= include_hypernova_integration_script %>
```

In the controller that renders your view using the above layout:
```rb
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"](https://guides.rubyonrails.org/caching_with_rails.html#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.
```rb
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

```bash
# 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

```bash
# 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.