Please take a moment to review this document in order to make the contribution process easy and effective for everyone involved.
Following these guidelines helps to get issues organised and PRs merged faster!
- Components should work across all platforms (web, iOS, Android) to the same level of minimum functionality (as determined by UX). This is to ensure that master is always in a releasable state and app projects can use our components with confidence. To enable this we're using react-native-web
- Components should provide a suite of sensible events for their interactions. This will allow metric components to report back for a given context
- Screenshots are required for visual changes on web, iOS and Android. Pictures are worth a thousand words
- Only add components that are wanted. They should form part of a larger feature and not be added in isolation because they might be useful in the future
- Use React perf and Chrome dev tools to identify issues AFTER the code is functionally complete
- In general we use yarn, add a yarn.lock file and keep it up to date for faster builds
- We use Prettier to ensure code consistency and reliability, this pattern should also be followed to avoid typical dev bike-shedding
We're using lerna for the monorepo with each
component in it's own package that should stand alone with it's own tests and
react story etc. A component is published in two ways. A compiled dist
version
for Native, so the platform doesn't need to worry about various Babel configs,
and a rnw.js
bundle for web. The package.json
main
points at the dist
entrypoint and relative paths are used to access the rnw
bundle.
For ease of use there is a CLI for creating a component. This is the quickest
way to create a package with the required scaffolding which is; a
component,package.json
, stubbed test and showcase/story. Note that the stubbed
test will fail until a snapshot is created withjest --updateSnapshot
or a test
run is made without the--CI
flag.
To use this, in the root of the project run: ./times-components create component ComponentName "Component Description"
When developing a component it's easiest to use the storybooks with hot reloading. Make sure you follow the React Native instructions to get up and running first. See README.md for commands to run the storybook.
We use styled-components to give us the ability to use media queries and keyframes to improve both UX and DX. This allows us to server-side-render pages but provide different experiences without the need for JavaScript.
It is possible to dynamically create a styled-component
at runtime but this
leads to poor SSR performance. On the server, one instance of the component will
be created, and on the client another, which breaks the benefits of hydration
and would result in another render. In addition, if you provide props to a
component that does this, you can't use lifecycle hooks such as
componentDidUpdate
because these components are always torn down (unmounted).
Avoid this pattern and use control flow to choose a specific styled-component
instead.
We use react-native-web
to compile our React Native components to web friendly
React. This relies on aggregating the given React Native styles and converting
them into CSS classes. Not only is it best practice to
use a stylesheet
whenever creating styles, it's also mandatory for hydration of SSR code. When
using object literals, the styles can be incorrectly aggregated on the server
and the client due to the different JS objects.
There are some problems regarding the usage of native storybooks with the Android simulator, mainly with hot module reloading (HMR). To take full advantage of HMR while developing components while testing with storybooks use the iOS simulator instead.
npm run storybook:build
will output the built web storybook into the default
storybook-static
folder that is synced to the gh_pages
branch to demo the
components in the web
When the CI passes packages:publish
will be run that uses
conventional commits to bump to the correct
semver version, create a CHANGELOG and push to the @times-components
org on
npm
When creating a new component you should specify the most suitable category in the showcase file. The current categories are:
- Primitive - components that are the basic building blocks from which other components can be composed
- Composed - components that are composed of Primitives
- Pages - complex page level components made up from multiple Composed and Primitive components
- Helpers - tools, utilities and helpers
For example to add a Slider
component to the the Composed
category you just
prefix the category name in the slider.showcase.js
file.
{
name: "Composed/Slider",
children: [
...showcases here
]
}
Good pull requests, such as patches, improvements, and new features, are a fantastic help. They should remain focused in scope and avoid containing unrelated commits. There should be a single PR for each component/package
Please ask first if somebody else is already working on this or the core developers think your feature is in-scope. Generally always have a related issue with discussions for whatever you are including.
Every component should have a XXXX.test.js
file with the component's Jest
tests split into the relevant platform folder e.g. android
, ios
, web
.
If the component contains functionality that requires functional testing on a
device or browser, you can use Fructose.
This should follow the naming convention, XXXX.fructose.js
.
Fructose also relies on an application existing within the project, in this case
we are using the storybook app. If you have not installed it, you can do so by
running yarn ios
. You will need to terminate the server that is run with this
as fructose will run its own at the time of testing. As long as you do not
delete the app, you will only have to build the app once.
If you run npm run functional-test:ios
in the root directory it will run
fructose tests for all of the components. This will similarly work for android
and web.
Dextrose is a visual snapshotting tool designed to make the visual inspection of components easier.
In order to run dextrose simply use yarn visual-snapshot-web
, yarn visual-snapshot-ios
or yarn visual-snapshot-android
. Each component that has
differences from master will be visually snapshotted and saved under the
/dextrose/snappy folder. This can be very useful for visually validating
components.
Follow these steps to deploy storybook native to a real android device.
- Plug the device into the computer
- Make sure your android device has trusted the connected computer and that
usb debugging / developer mode
has been turned on. - For Android <4.2 go to Developer Options => Enable USB Debugging, and for Android >=4.2 go to About Phone/Tablet => Tap Build Number 7 Times => Developer Options => Enable USB Debugging
- Install android tooling through
brew cask install android-platform-tools
- Optionally start a local instance of The Times Public Api (dependent on the stories you intend to view)
- Run
yarn
- Run
yarn storybook-native
- Run
yarn android:device
(to enable Times API) - Open storybook native on your computer and load a story
- If your device is complaining about about
story-loader.js
not existing - runyarn storybook-native
beforeyarn android
. This should generate the missing file. - If your device is complaining about being unable to reach
localhost
or404
use theadb
commands. Shake the device to bring up the developer menu and reload the app - If still struggling; shake the device and
debug js remotely
. Open a console on your computer for more info on the error - If you're still struggling verify that you are able to run
yarn storybook
and that it works in web view.
We are currently using React Native 0.55.4 which does not fully support Xcode 101, 2. If you need to build with Xcode 10, you will need to run the below command, only after you have installed dependencies.
$ yarn ios; pushd node_modules/react-native/third-party/glog-*; ../../scripts/ios-configure-glog.sh; popd; cp ios/build/Build/Products/Debug-iphonesimulator/libfishhook.a node_modules/react-native/Libraries/WebSocket/;
If when trying to run yarn ios
you receive a ":CFBundleIdentifier", Does Not Exist
error, either on XCode 10 after attempting the above instructions, or on
a previous version of Xcode, try clearing your React Native cache with
rm -r ~/.rncache
and clearing third party libraries
rm -r node_modules/react-native/third_party
This happens when React Native caches third party tools for previous versions of React Native.
If the above does not work, another approach is to change the XCode build
system. Essentially you delete build artifacts by deleting the contents of your
Library/Developer/Xcode/DerivedData
folder.
Then choose the legacy build system. Then clean the XCode project (CMD-Shift-K) and build it again. This did result in a new directory being created but we have .gitignored it for now (until RN fix their issues with XCode 10).
Android and iOS interpret fonts differently. The style property for fontFamily
on iOS refers to the internal post script name of the font. The style property
for fontFamily
on Android refers to the filename of the font. React Native
Android only supports
4 font weights,
therefore we have made the following conventions
Also the fonts should have the correct weight
and style
meta attributes
- TimesDigitalW04.ttf
- TimesDigitalW04_italic.ttf
- TimesDigitalW04_bold.ttf
EXCEPT FOR REGULAR eg
TimesDigitalW04.ttf for a TimesDigitalW04-Regular.ttf font
- TimesDigitalW04-Regular
- TimesDigitalW04-Italic
- TimesDigitalW04-Bold
- TimesDigitalW04
The filename, font name and family name should refer to the complete font file name
- Filename format:
<fontname>-<weight>
- Font Name / Full name format :
<fontname>-<weight>
- Family Name format:
<fontname>-<weight>
eg
- Filename = GillSansMTStd-Medium
- FontName / Full name = GillSansMTStd-Medium
- FamilyName = GillSansMTStd-Medium
An example component/package looks like this:
.
└── card
├── __tests__
│ └── android
│ └── __snapshots__
│ └── card.android.test.js.snap
│ └── card-with-style.android.test.js.snap
│ └── card.android.test.js
│ └── card-with-style.android.test.js
│ └── ios
│ └── web
│ └── shared.test.js
│ └── shared.native.js
│ └── shared.web.js
├── src
│ └── card.js
├── card.showcase.js
├── card.stories.js
├── CHANGELOG.md
├── package.json
├── README.md
├── rnw.js
└── webpack.config.js
- .storybook houses the web storybook config that dynamically loads the component stories and aliases the native imports to web
- .storybook.native is home to the generated
story-loader.js
from theprestorybook-native
npm script, and the storybook bootstrapping - android, ios, .babelrc, .buckconfig, .gitattributes, .watchmanconfig, app.json are all from a stock react-native project in order to run the native storybook
- lerna.json specifies that the packages are independent so different platforms can control which versions they consume and allows them to develop organically
-
Clone the repo with
https://github.com/newsuk/times-components.git
-
Run
yarn
in the root folder. Set aGRAPHQL_ENDPOINT
envar for linting.
Once it is done, you can run npm run storybook
and/or npm run storybook-native
The Provider Queries package
is home to any GraphQL queries that should be
shared across the various platforms. The
Times Public API schema is
generated in the schema
package which should be updated when any fields are
changed. The packages linting will then check that each of the gql
queries
within the package are properly formed
Each component should be bumped and published correctly when it is merged to master and has passed the CI process
This was heavily sourced from CRA