Skip to content

Latest commit

Β 

History

History
565 lines (428 loc) Β· 22.4 KB

DEVELOPMENT.md

File metadata and controls

565 lines (428 loc) Β· 22.4 KB

Developing the Terminus UI

Table of Contents

Developing

# Build the library
$ yarn run library:build

# Build and watch library files for changes
$ yarn run library:build:watch

# Run TypeScript and SCSS linters
$ yarn run library:lint

# Run all TypeScript tests
$ yarn run library:test

# Run all TypeScript tests and output coverage
$ yarn run library:test:ci:local

# Start the demo project and watch demo and lib files for changes
# (don't forget to build the library first!)
$ yarn run demo:start

# Test the demo app
$ yarn run demo:test

# Lint the demo app
$ yarn run demo:lint

# Create a commit with a helpful CLI tool
$ yarn run cm

# Add yourself as a contributor
# See contribution types here: http://bnj.bz/3C1S0A0d1c3U
$ yarn run contributors:add [your-github-handle] [contribution-type]
$ yarn run contributors:generate

Check package.json for all available commands

Demos

  1. yarn install && yarn run library:build && yarn run demo:start
  2. Navigate to http://localhost:4200/components/
  3. Select a component from the menu (top right)

Demos Screenshot

Adding a Component

  1. Create a directory using the component name: projects/library/button/
    • Necessary files:
      • button.module.ts
        • Class name: TsButtonModule
      • button.component.ts
        • Class name: TsButtonComponent
      • button.component.scss
      • button.component.html
  2. Import and export button.component.ts inside button.module.ts
  3. Add TsButtonComponent to the exports and declarations of button.module.ts.
  4. Comment all methods, constants & @Inputs using the supported JSDoc style.
  5. Add a usage example in the component documentation with every possible input and output included.
  6. Add the new component to available scopes in tooling/cz-config.js
  7. Update the status for the component in the components table

Branching

Note: Our base branch, release, is always deployable.

Naming

  1. Branches should have a brief but comprehensive name.
    • The name should clearly encompass the work that the branch will contain. A name like user-view may make sense now, but some time later it will be difficult to know what work was done on the branch. A better name could be 27-create-user-detail-view. Be as specific as possible, while keeping the length as short as possible.
  2. Branches should have the associated GitHub issue number in the name.
    • This is a helpful reference to the issue details. More importantly, it forces everyone to create the issue before the work begins. If it is worth doing, than it deserves an issue. This creates better historical data and more importantly, gives everyone visibility into the work being done.
    • The number should be at the beginning of the branch name: 45-update-payment-gateways. This enables quick auto-completion in most terminals.

Workflow

Beginning a feature
  1. Checkout release
  2. Pull release
  3. Create a feature branch from release (see branch naming)
Working on a feature
  1. Commit all work on the feature branch
  2. Create a pull request at any time (just don't request a review until you are finished)
  3. Keep the remote up to date with your latest changes. Committing often locally is good, but those commits should be pushed to the remote (i.e. GitHub) at least once a day so that the code is available to all engineers.
Finish a feature
  1. If there are conflicts, merge release into the feature branch
    • Only do this if there are conflicts
    • See Pull Requests for more information
  2. Verify all linters run successfully
  3. Verify all tests are passing and code coverage did not decrease (bonus points if it increases)
  4. If you haven't yet, create a pull request from the feature branch into release
  5. Add as much information into the pull request body as possible
  6. Request a review

Hotfixes

Hotfixes follow the same strategy as features.

Committing

When code gets merged to release, many of our projects are automatically versioned and released. In order to give our tooling the information it needs, we write our commit messages in a specific format. This has the added benefit of improving the readability of our commit history.

# The format:
type(scope): message

# Examples:
fix(Button): Aria label is now correctly read by JAWS
feat(Tooltip): Add a tooltip component
chore(): Bump lodash version

For a friendlier prompt, you can run yarn run cm in the repo. This will ask several questions and then construct the commit in the proper format for you. The prompt will allow you to choose from a list of types and then a list of scopes. This is much easier than trying to remember all possible scopes.

Optional: Installing committizen (yarn add -g committizen) will allow you to run git cz or even alias to your preferred git command.

You must use one of the defined types since the types have specific meaning to the automatic versioning tool.

  • A type fix will increase the patch version (x.x.1)
  • A type feat will increase the minor version (x.1.x)

To see all valid types for a project, look for the file cz-config.js.

Breaking Changes

When a commit contains a breaking change, it must be included in the commit message body (not the title). Both words should be uppercase and the comment body should include everything that other engineers may need to know: BREAKING CHANGE: A description of the breaking change. This could include comments, images, code examples, and more. Generally speaking, more information is better. (Note: when using the cli prompt, it will ask you about any breaking changes and add the prefix BREAKING CHANGES: automatically)

Learn more about the automatic versioning tools we use:

Linting

All projects should pass all available linters before committing.

TypeScript & JavaScript projects use ESLint and SASS/SCSS projects use Stylelint.

To edit configuration, look for the files .eslintrc, or stylelint.config.js. These files should not be edited without a discussion with the team.

Look at the scripts section in the project's package.json for the command to run tests.

Testing

All projects should work towards full test coverage. This is often not practical in a quickly changing software startup, but a project should never dip below 80% coverage.

We use Jest for unit tests.

Look at the scripts section in the project's package.json for the command to run tests.

Pull Requests

When it is time merge a branch into release, create a pull request from the feature into release.

  1. At the top of the pull request, link to the original issue.
    • If the ZenHub extension is installed in your browser, you can attach an issue to the PR via the built in controls
  2. If the pull request includes more than one item, include a high level list of what was done.
  3. If the pull request covers UI changes, include a GIF or image to clearly show the change (bonus points for before and after images).
  4. Request a review from someone on the UI team (any code owners will be notified by default).
  5. A pull request may be opened before the work is complete. This makes it easier to get feedback while the work is in progress. Include WIP: at the beginning of the pull request title, add the DO NOT MERGE label so that it is not accidentally merged and cc/ @mention anyone that should take a look.
  6. There are two options to check for merge conflicts between your branch and release:
    • Create a pull request against release. (Note: This will cause any associated CI service to begin building the feature branch on every push)
    • Use GitHub's compare view: https://github.com/GetTerminus/terminus-ui/compare/your-branch-name...release
  7. The pull request body, just like the issue body, is the single source of truth. Any discussions, decisions or relevant information should be added to the pull request body immediately.

Releasing

Releases are handled automatically when code is merged to release. Never merge code to release that is not production ready!

  1. Semantic Release looks at all commits since the last tag on release.
  2. Based on those commits it will bump the version number appropriately.
  3. A changelog is generated in the release notes on Github.
  4. The new version is published to NPM under the next tag.
  5. When the new functionality is verified, it is tagged as latest:
    • npm dist-tag add @terminus/ui@<version to promote> latest

NOTE: currently yarn tag outputs an error even though the tagging seems to work. Because of this, we will continue using NPM for tagging.

You can view the currently published files using the unpkg CDN.

Promoting Releases

By default all releases are tagged as @next on NPM. This allows us to test the release with consumer partners before promoting for all consumers. To promote the most recent @next release to @latest, run the release script prefixed with a valid GitHub token:

# NOTE: This is meant to be run from the repo root - not on CI.
# Pass your GitHub token as `GH_TOKEN` with the call:
$ GH_TOKEN=[YOUR_TOKEN] tooling/ci/promote-next-and-deploy.sh

NOTE: A personal GitHub token can be generated by visiting https://github.com/settings/tokens.

See the full script for more context.

Code Comments

When writing code, an engineer should be mindful that others will need to understand and edit this code in the future. For this reason we don't assume that the future editor has the context.

Modern tooling will handle cleaning up comments and white space so we write for humans - not machines.

  1. Always include a JSDoc-style comment above all methods/functions:

    /**
     * Get a customer by ID
     *
     * @param customerId - The customer's ID
     * @returns The customer object
     */
    
  2. For general notes, always include the prefix if one applies:

    // NOTE: A general informational note. Not for every comment; just when there is an important
             piece of unusual information.
    // TODO: A note about missing logic.
    // HACK: A note about a 'hacky' solution.
    // FIXME: A note about something that needs refactoring.
    // BUG: A note about a bug. Include the associated issue number in the note.
    
  3. All engineers should do their best to never need most of these note types. However, if needed, always include the reason the note is needed. Remember, more information is better!

JSDoc Tags

Type

We don't use the type tag since the types are documented ina the TypeScript code.

Deprecated

The @deprecated flag can be used to mark deprecated code. It should be accompanied by a migration note if possible.

/**
 * @deprecated This method has been merged into `myNewMethod()`
 */
Ignore

The @ignore flag should be used on all code that is not part of the library (such as test host components etc).

/**
 * @ignore
 */
export class MyTestHost {}
Internal

The @internal flag should be used on code that is not ignored (as it is part of the library) but still should not appear in the generated documentation.

/**
 * @internal
 */
export function myPrivateHelperFunction() {}
Link

The @link flag can be used to link to other components or web locations.

/**
 * Component B.
 * 
 * Used by {@link ComponentA}
 * Learn more at [Google]{@link http://www.google.com}
 */
export class ComponentB() {}

Usage Docs

Usage docs should be added to all components and directives. These docs are created by adding a markdown file named to match the component or directive:

// component:
foo.component.ts
// usage docs:
foo.component.md

When the documentation published, this usage doc will be automatically consumed by the API docs generation.

When editing or creating usage documentation, add headlines and appropriate content, then run yarn run docs:toc to update (or generate) the table of contents.

Releases

Any code merged to the release branch gets published under the next tag:

$ yarn add @terminus/ui@next

Once the code is ready to be promoted to latest a member of our NPM organization can promote it:

# Replace `0.0.0` with the version to promote
$ npm dist-tag add @terminus/ui@0.0.0 latest

Code Style

Most code style is enforced through ESLint, TSLint and Stylelint, so make sure to run all of the linters.

Lint configurations

Member Ordering

When writing a class, this is the basic order of items:

  1. Private properties
  2. Public properties
  3. Getters (setter goes before getter, which is before the associated private property)
  4. View references (@ViewChild etc)
  5. @Inputs
  6. @Outputs
  7. Constructor
  8. Lifecycle methods (in the order they are called by Angular) (ngOnInit etc)
  9. Public methods
  10. Protected or static methods
  11. Private methods

Underscores

Prefixing private methods and properties is a common pattern. We have opted to rely on TypeScript's member access rather than the underscore method.

Caveat: The one place we do use the underscore is to prefix the private property that is used with a getter and setter:

public set active(v: boolean) {
  this._foo = v;
}
public get active(): boolean {
  return this._foo;
}
private _foo = false;

Setters & Getters

Our setters always come before the getter; which is in turn before the private property. (See the above code example)

Decorators

Decorators go on their own line above the item they are decorating:

@Input()
public foo;

Issues

  1. Always create an issue for things you work on. If it is worth spending time on, it is worth creating an issue for it since that enables other people to learn and help. You can always edit the description or close it when the problem is something different or disappears.
  2. Always include a link to the original issue when submitting a pull request.
  3. If two issues are related, cross link them (a link from each issue to the other one). Put the link at the top of each issue's description with a short mention of the relationship (Report, etc.). If there are more than 2 issues, use one issue as the central one and cross link all issues to this one.
  4. After a discussion about a feature update the issue body with the consensus or final conclusions. This makes it much easier to see the state of an issue for everyone involved in the implementation and prevents confusion and discussion later on.
  5. Submit the smallest item of work that makes sense. When creating an issue describe the smallest fix possible, put suggestions for enhancements in separate issues and link them.
  6. Do not leave issues open for a long time, issues should be actionable and realistic. If you are assigned to an issue but don't have time to work on it, remove your assignment so that the next available engineer can pick it up.
  7. Make a conscious effort to prioritize your work. The priority of items depends on multiple factors: Is there a team member waiting for the answer? What is the impact if you delay it? How many people does it affect, etc.?
  8. If the project is using milestones, pick issues from the current milestone.
  9. Assign an issue to yourself as soon as you start to work on it, but not before that time. If you complete part of an issue and need someone else to take the next step, re-assign the issue to that person.
  10. When re-assigning an issue, make sure that the issue body contains the latest information. The issue body should be the single source of truth.
  11. When working on an issue, ask for feedback from your peers. For example, if you're a designer and you propose a design, ping a fellow designer to review your work. If they approve, you can move it to the next step. If they suggest changes, you get the opportunity to improve your design. This promotes collaboration and advances everyone's skills.
  12. Even when something is not done, share it internally so people can comment early and prevent rework. Mark the pull request Work In Progress or WIP so it is not merged by accident.
  13. When you create a pull request, mention the issue(s) that it solves in the description. After the merge, if any followup actions are required on the issue like reporting back to any customers or writing documentation, avoid auto closing it by including text such as 'Fixes #1' or 'Closes #1'.
  14. When the pull request is complete, remove the WIP prefix and assign the pull request to someone to review and merge it. You can still make changes based on feedback, but by removing the WIP prefix it clarifies that the main body of work has been completed.
  15. If a pull request is assigned to you and there is a merge conflict, consider trying to resolve it yourself instead of asking the pull request creator to resolve the conflict. If it is easy to resolve, you avoid a round trip between you and the creator, and the pull request gets merged sooner. This is a suggestion, not an obligation.
  16. If you ask a question to a specific person, always start the comment by mentioning them; this will ensure they see it and other people will understand they don't have to respond.
  17. Do not close an issue until it is fully complete, which means code has been merged, tested, all issue trackers are updated, and any documentation is written and merged.
  18. When closing an issue, leave a comment explaining why you are closing the issue.
  19. If you notice that the tests for the release branch of any project are failing (red) or broken (green as a false positive), fixing this takes priority over everything else development related, since everything we do while test are broken may break functionality, or introduce new bugs and security issues. If the problem cannot be fixed by you within a few hours, because if it is too much work for one person and/or you have other priorities, create an issue, post about it in development Slack channel.

πŸŽ‰ Happy Coding! πŸŽ‰